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_rcg2.h"
41e34a491bSAdrian Chadd #include "qcom_clk_rcg2_reg.h"
42e34a491bSAdrian Chadd
43e34a491bSAdrian Chadd #include "clkdev_if.h"
44e34a491bSAdrian Chadd
45e34a491bSAdrian Chadd #if 0
46e34a491bSAdrian Chadd #define DPRINTF(dev, msg...) device_printf(dev, msg);
47e34a491bSAdrian Chadd #else
48e34a491bSAdrian Chadd #define DPRINTF(dev, msg...)
49e34a491bSAdrian Chadd #endif
50e34a491bSAdrian Chadd
51e34a491bSAdrian Chadd #define QCOM_CLK_RCG2_CFG_OFFSET(sc) \
52e34a491bSAdrian Chadd ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_CFG_REG)
53e34a491bSAdrian Chadd #define QCOM_CLK_RCG2_CMD_REGISTER(sc) \
54e34a491bSAdrian Chadd ((sc)->cmd_rcgr + QCOM_CLK_RCG2_CMD_REG)
55e34a491bSAdrian Chadd #define QCOM_CLK_RCG2_M_OFFSET(sc) \
56e34a491bSAdrian Chadd ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_M_REG)
57e34a491bSAdrian Chadd #define QCOM_CLK_RCG2_N_OFFSET(sc) \
58e34a491bSAdrian Chadd ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_N_REG)
59e34a491bSAdrian Chadd #define QCOM_CLK_RCG2_D_OFFSET(sc) \
60e34a491bSAdrian Chadd ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_D_REG)
61e34a491bSAdrian Chadd
62e34a491bSAdrian Chadd struct qcom_clk_rcg2_sc {
63e34a491bSAdrian Chadd struct clknode *clknode;
64e34a491bSAdrian Chadd uint32_t cmd_rcgr;
65e34a491bSAdrian Chadd uint32_t hid_width;
66e34a491bSAdrian Chadd uint32_t mnd_width;
67e34a491bSAdrian Chadd int32_t safe_src_idx;
68e34a491bSAdrian Chadd uint32_t cfg_offset;
69e34a491bSAdrian Chadd int safe_pre_parent_idx;
70e34a491bSAdrian Chadd uint32_t flags;
71e34a491bSAdrian Chadd const struct qcom_clk_freq_tbl *freq_tbl;
72e34a491bSAdrian Chadd };
73e34a491bSAdrian Chadd
74e34a491bSAdrian Chadd
75e34a491bSAdrian Chadd /*
76e34a491bSAdrian Chadd * Finish a clock update.
77e34a491bSAdrian Chadd *
78e34a491bSAdrian Chadd * This instructs the configuration to take effect.
79e34a491bSAdrian Chadd */
80e34a491bSAdrian Chadd static bool
qcom_clk_rcg2_update_config_locked(struct qcom_clk_rcg2_sc * sc)81e34a491bSAdrian Chadd qcom_clk_rcg2_update_config_locked(struct qcom_clk_rcg2_sc *sc)
82e34a491bSAdrian Chadd {
83e34a491bSAdrian Chadd uint32_t reg, count;
84e34a491bSAdrian Chadd
85e34a491bSAdrian Chadd /*
86e34a491bSAdrian Chadd * Send "update" to the controller.
87e34a491bSAdrian Chadd */
88e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
89e34a491bSAdrian Chadd QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
90e34a491bSAdrian Chadd reg |= QCOM_CLK_RCG2_CMD_UPDATE;
91e34a491bSAdrian Chadd CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
92e34a491bSAdrian Chadd QCOM_CLK_RCG2_CMD_REGISTER(sc), reg);
93e34a491bSAdrian Chadd wmb();
94e34a491bSAdrian Chadd
95e34a491bSAdrian Chadd /*
96e34a491bSAdrian Chadd * Poll for completion of update.
97e34a491bSAdrian Chadd */
98e34a491bSAdrian Chadd for (count = 0; count < 1000; count++) {
99e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
100e34a491bSAdrian Chadd QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
101e34a491bSAdrian Chadd if ((reg & QCOM_CLK_RCG2_CMD_UPDATE) == 0) {
102e34a491bSAdrian Chadd return (true);
103e34a491bSAdrian Chadd }
104e34a491bSAdrian Chadd DELAY(10);
105e34a491bSAdrian Chadd rmb();
106e34a491bSAdrian Chadd }
107e34a491bSAdrian Chadd
108e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
109e34a491bSAdrian Chadd QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
110e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode), "%s: failed; reg=0x%08x\n",
111e34a491bSAdrian Chadd __func__, reg);
112e34a491bSAdrian Chadd return (false);
113e34a491bSAdrian Chadd }
114e34a491bSAdrian Chadd
115e34a491bSAdrian Chadd /*
116e34a491bSAdrian Chadd * Calculate the output frequency given an input frequency and the m/n:d
117e34a491bSAdrian Chadd * configuration.
118e34a491bSAdrian Chadd */
119e34a491bSAdrian Chadd static uint64_t
qcom_clk_rcg2_calc_rate(uint64_t rate,uint32_t mode,uint32_t m,uint32_t n,uint32_t hid_div)120e34a491bSAdrian Chadd qcom_clk_rcg2_calc_rate(uint64_t rate, uint32_t mode, uint32_t m, uint32_t n,
121e34a491bSAdrian Chadd uint32_t hid_div)
122e34a491bSAdrian Chadd {
123e34a491bSAdrian Chadd if (hid_div != 0) {
124e34a491bSAdrian Chadd rate = rate * 2;
125e34a491bSAdrian Chadd rate = rate / (hid_div + 1);
126e34a491bSAdrian Chadd }
127e34a491bSAdrian Chadd
128e34a491bSAdrian Chadd /* Note: assume n is not 0 here; bad things happen if it is */
129e34a491bSAdrian Chadd
130e34a491bSAdrian Chadd if (mode != 0) {
131e34a491bSAdrian Chadd rate = (rate * m) / n;
132e34a491bSAdrian Chadd }
133e34a491bSAdrian Chadd
134e34a491bSAdrian Chadd return (rate);
135e34a491bSAdrian Chadd }
136e34a491bSAdrian Chadd
137e34a491bSAdrian Chadd /*
138e34a491bSAdrian Chadd * The inverse of calc_rate() - calculate the required input frequency
139e34a491bSAdrian Chadd * given the desired output freqency and m/n:d configuration.
140e34a491bSAdrian Chadd */
141e34a491bSAdrian Chadd static uint64_t
qcom_clk_rcg2_calc_input_freq(uint64_t freq,uint32_t m,uint32_t n,uint32_t hid_div)142e34a491bSAdrian Chadd qcom_clk_rcg2_calc_input_freq(uint64_t freq, uint32_t m, uint32_t n,
143e34a491bSAdrian Chadd uint32_t hid_div)
144e34a491bSAdrian Chadd {
145e34a491bSAdrian Chadd if (hid_div != 0) {
146e34a491bSAdrian Chadd freq = freq / 2;
147e34a491bSAdrian Chadd freq = freq * (hid_div + 1);
148e34a491bSAdrian Chadd }
149e34a491bSAdrian Chadd
150e34a491bSAdrian Chadd if (n != 0) {
151e34a491bSAdrian Chadd freq = (freq * n) / m;
152e34a491bSAdrian Chadd }
153e34a491bSAdrian Chadd
154e34a491bSAdrian Chadd return (freq);
155e34a491bSAdrian Chadd }
156e34a491bSAdrian Chadd
157e34a491bSAdrian Chadd static int
qcom_clk_rcg2_recalc(struct clknode * clk,uint64_t * freq)158e34a491bSAdrian Chadd qcom_clk_rcg2_recalc(struct clknode *clk, uint64_t *freq)
159e34a491bSAdrian Chadd {
160e34a491bSAdrian Chadd struct qcom_clk_rcg2_sc *sc;
161e34a491bSAdrian Chadd uint32_t cfg, m = 0, n = 0, hid_div = 0;
162e34a491bSAdrian Chadd uint32_t mode = 0, mask;
163e34a491bSAdrian Chadd
164e34a491bSAdrian Chadd sc = clknode_get_softc(clk);
165e34a491bSAdrian Chadd
166e34a491bSAdrian Chadd /* Read the MODE, CFG, M and N parameters */
167e34a491bSAdrian Chadd CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
168e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
169e34a491bSAdrian Chadd QCOM_CLK_RCG2_CFG_OFFSET(sc),
170e34a491bSAdrian Chadd &cfg);
171e34a491bSAdrian Chadd if (sc->mnd_width != 0) {
172e34a491bSAdrian Chadd mask = (1U << sc->mnd_width) - 1;
173e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
174e34a491bSAdrian Chadd QCOM_CLK_RCG2_M_OFFSET(sc), &m);
175e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
176e34a491bSAdrian Chadd QCOM_CLK_RCG2_N_OFFSET(sc), &n);
177e34a491bSAdrian Chadd m = m & mask;
178e34a491bSAdrian Chadd n = ~ n;
179e34a491bSAdrian Chadd n = n & mask;
180e34a491bSAdrian Chadd n = n + m;
181e34a491bSAdrian Chadd mode = (cfg & QCOM_CLK_RCG2_CFG_MODE_MASK)
182e34a491bSAdrian Chadd >> QCOM_CLK_RCG2_CFG_MODE_SHIFT;
183e34a491bSAdrian Chadd }
184e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
185e34a491bSAdrian Chadd
186e34a491bSAdrian Chadd /* Fetch the divisor */
187e34a491bSAdrian Chadd mask = (1U << sc->hid_width) - 1;
188e34a491bSAdrian Chadd hid_div = (cfg >> QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT) & mask;
189e34a491bSAdrian Chadd
190e34a491bSAdrian Chadd /* Calculate the rate based on the parent rate and config */
191e34a491bSAdrian Chadd *freq = qcom_clk_rcg2_calc_rate(*freq, mode, m, n, hid_div);
192e34a491bSAdrian Chadd
193e34a491bSAdrian Chadd return (0);
194e34a491bSAdrian Chadd }
195e34a491bSAdrian Chadd
196e34a491bSAdrian Chadd /*
197e34a491bSAdrian Chadd * configure the mn:d divisor, pre-divisor, and parent.
198e34a491bSAdrian Chadd */
199e34a491bSAdrian Chadd static void
qcom_clk_rcg2_set_config_locked(struct qcom_clk_rcg2_sc * sc,const struct qcom_clk_freq_tbl * f,int parent_idx)200e34a491bSAdrian Chadd qcom_clk_rcg2_set_config_locked(struct qcom_clk_rcg2_sc *sc,
201e34a491bSAdrian Chadd const struct qcom_clk_freq_tbl *f, int parent_idx)
202e34a491bSAdrian Chadd {
203e34a491bSAdrian Chadd uint32_t mask, reg;
204e34a491bSAdrian Chadd
205e34a491bSAdrian Chadd /* If we have MN:D, then update it */
206e34a491bSAdrian Chadd if (sc->mnd_width != 0 && f->n != 0) {
207e34a491bSAdrian Chadd mask = (1U << sc->mnd_width) - 1;
208e34a491bSAdrian Chadd
209e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
210e34a491bSAdrian Chadd QCOM_CLK_RCG2_M_OFFSET(sc), ®);
211e34a491bSAdrian Chadd reg &= ~mask;
212e34a491bSAdrian Chadd reg |= (f->m & mask);
213e34a491bSAdrian Chadd CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
214e34a491bSAdrian Chadd QCOM_CLK_RCG2_M_OFFSET(sc), reg);
215e34a491bSAdrian Chadd
216e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
217e34a491bSAdrian Chadd QCOM_CLK_RCG2_N_OFFSET(sc), ®);
218e34a491bSAdrian Chadd reg &= ~mask;
219e34a491bSAdrian Chadd reg |= ((~(f->n - f->m)) & mask);
220e34a491bSAdrian Chadd CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
221e34a491bSAdrian Chadd QCOM_CLK_RCG2_N_OFFSET(sc), reg);
222e34a491bSAdrian Chadd
223e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
224e34a491bSAdrian Chadd QCOM_CLK_RCG2_D_OFFSET(sc), ®);
225e34a491bSAdrian Chadd reg &= ~mask;
226e34a491bSAdrian Chadd reg |= ((~f->n) & mask);
227e34a491bSAdrian Chadd CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
228e34a491bSAdrian Chadd QCOM_CLK_RCG2_D_OFFSET(sc), reg);
229e34a491bSAdrian Chadd }
230e34a491bSAdrian Chadd
231e34a491bSAdrian Chadd mask = (1U << sc->hid_width) - 1;
232e34a491bSAdrian Chadd /*
233e34a491bSAdrian Chadd * Mask out register fields we're going to modify along with
234e34a491bSAdrian Chadd * the pre-divisor.
235e34a491bSAdrian Chadd */
236e34a491bSAdrian Chadd mask |= QCOM_CLK_RCG2_CFG_SRC_SEL_MASK
237e34a491bSAdrian Chadd | QCOM_CLK_RCG2_CFG_MODE_MASK
238e34a491bSAdrian Chadd | QCOM_CLK_RCG2_CFG_HW_CLK_CTRL_MASK;
239e34a491bSAdrian Chadd
240e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
241e34a491bSAdrian Chadd QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
242e34a491bSAdrian Chadd reg &= ~mask;
243e34a491bSAdrian Chadd
244e34a491bSAdrian Chadd /* Configure pre-divisor */
245e34a491bSAdrian Chadd reg = reg | ((f->pre_div) << QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT);
246e34a491bSAdrian Chadd
247e34a491bSAdrian Chadd /* Configure parent clock */
248e34a491bSAdrian Chadd reg = reg | (((parent_idx << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
249e34a491bSAdrian Chadd & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK));
250e34a491bSAdrian Chadd
251e34a491bSAdrian Chadd /* Configure dual-edge if needed */
252e34a491bSAdrian Chadd if (sc->mnd_width != 0 && f->n != 0 && (f->m != f->n))
253e34a491bSAdrian Chadd reg |= QCOM_CLK_RCG2_CFG_MODE_DUAL_EDGE;
254e34a491bSAdrian Chadd
255e34a491bSAdrian Chadd CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
256e34a491bSAdrian Chadd QCOM_CLK_RCG2_CFG_OFFSET(sc), reg);
257e34a491bSAdrian Chadd }
258e34a491bSAdrian Chadd
259e34a491bSAdrian Chadd static int
qcom_clk_rcg2_init(struct clknode * clk,device_t dev)260e34a491bSAdrian Chadd qcom_clk_rcg2_init(struct clknode *clk, device_t dev)
261e34a491bSAdrian Chadd {
262e34a491bSAdrian Chadd struct qcom_clk_rcg2_sc *sc;
263e34a491bSAdrian Chadd uint32_t reg;
264e34a491bSAdrian Chadd uint32_t idx;
2653d9bd825SAdrian Chadd bool enabled __unused;
266e34a491bSAdrian Chadd
267e34a491bSAdrian Chadd sc = clknode_get_softc(clk);
268e34a491bSAdrian Chadd
269e34a491bSAdrian Chadd /*
270e34a491bSAdrian Chadd * Read the mux setting to set the right parent.
271e34a491bSAdrian Chadd * Whilst here, read the config to get whether we're enabled
272e34a491bSAdrian Chadd * or not.
273e34a491bSAdrian Chadd */
274e34a491bSAdrian Chadd CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
275e34a491bSAdrian Chadd /* check if rcg2 root clock is enabled */
276e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
277e34a491bSAdrian Chadd QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
278e34a491bSAdrian Chadd if (reg & QCOM_CLK_RCG2_CMD_ROOT_OFF)
279e34a491bSAdrian Chadd enabled = false;
280e34a491bSAdrian Chadd else
281e34a491bSAdrian Chadd enabled = true;
2823d9bd825SAdrian Chadd
283e34a491bSAdrian Chadd /* mux settings */
284e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
285e34a491bSAdrian Chadd QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
286e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
287e34a491bSAdrian Chadd
288e34a491bSAdrian Chadd idx = (reg & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK)
289e34a491bSAdrian Chadd >> QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT;
2903d9bd825SAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
2913d9bd825SAdrian Chadd "%s: mux index %u, enabled=%d\n",
2923d9bd825SAdrian Chadd __func__, idx, enabled);
293e34a491bSAdrian Chadd clknode_init_parent_idx(clk, idx);
294e34a491bSAdrian Chadd
295e34a491bSAdrian Chadd /*
296e34a491bSAdrian Chadd * If we could be sure our parent clocks existed here in the tree,
297e34a491bSAdrian Chadd * we could calculate our current frequency by fetching the parent
298e34a491bSAdrian Chadd * frequency and then do our divider math. Unfortunately that
299e34a491bSAdrian Chadd * currently isn't the case.
300e34a491bSAdrian Chadd */
301e34a491bSAdrian Chadd
302e34a491bSAdrian Chadd return(0);
303e34a491bSAdrian Chadd }
304e34a491bSAdrian Chadd
305e34a491bSAdrian Chadd static int
qcom_clk_rcg2_set_gate(struct clknode * clk,bool enable)306e34a491bSAdrian Chadd qcom_clk_rcg2_set_gate(struct clknode *clk, bool enable)
307e34a491bSAdrian Chadd {
308e34a491bSAdrian Chadd
309e34a491bSAdrian Chadd /*
310e34a491bSAdrian Chadd * For now this isn't supported; there's some support for
311e34a491bSAdrian Chadd * "shared" rcg2 nodes in the Qualcomm/upstream Linux trees but
312e34a491bSAdrian Chadd * it's not currently needed for the supported platforms.
313e34a491bSAdrian Chadd */
314e34a491bSAdrian Chadd return (0);
315e34a491bSAdrian Chadd }
316e34a491bSAdrian Chadd
317e34a491bSAdrian Chadd /*
318e34a491bSAdrian Chadd * Program the parent index.
319e34a491bSAdrian Chadd *
320e34a491bSAdrian Chadd * This doesn't do the update. It also must be called with the device
321e34a491bSAdrian Chadd * lock held.
322e34a491bSAdrian Chadd */
323e34a491bSAdrian Chadd static void
qcom_clk_rcg2_set_parent_index_locked(struct qcom_clk_rcg2_sc * sc,uint32_t index)324e34a491bSAdrian Chadd qcom_clk_rcg2_set_parent_index_locked(struct qcom_clk_rcg2_sc *sc,
325e34a491bSAdrian Chadd uint32_t index)
326e34a491bSAdrian Chadd {
327e34a491bSAdrian Chadd uint32_t reg;
328e34a491bSAdrian Chadd
329e34a491bSAdrian Chadd CLKDEV_READ_4(clknode_get_device(sc->clknode),
330e34a491bSAdrian Chadd QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
331e34a491bSAdrian Chadd reg = reg & ~QCOM_CLK_RCG2_CFG_SRC_SEL_MASK;
332e34a491bSAdrian Chadd reg = reg | (((index << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
333e34a491bSAdrian Chadd & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK));
334e34a491bSAdrian Chadd CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
335e34a491bSAdrian Chadd QCOM_CLK_RCG2_CFG_OFFSET(sc),
336e34a491bSAdrian Chadd reg);
337e34a491bSAdrian Chadd }
338e34a491bSAdrian Chadd
339e34a491bSAdrian Chadd /*
340e34a491bSAdrian Chadd * Set frequency
341e34a491bSAdrian Chadd *
342e34a491bSAdrian Chadd * fin - the parent frequency, if exists
343e34a491bSAdrian Chadd * fout - starts as the requested frequency, ends with the configured
344e34a491bSAdrian Chadd * or dry-run frequency
345e34a491bSAdrian Chadd * Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN
346e34a491bSAdrian Chadd * retval - 0, ERANGE
347e34a491bSAdrian Chadd */
348e34a491bSAdrian Chadd static int
qcom_clk_rcg2_set_freq(struct clknode * clk,uint64_t fin,uint64_t * fout,int flags,int * stop)349e34a491bSAdrian Chadd qcom_clk_rcg2_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
350e34a491bSAdrian Chadd int flags, int *stop)
351e34a491bSAdrian Chadd {
352e34a491bSAdrian Chadd struct qcom_clk_rcg2_sc *sc;
353e34a491bSAdrian Chadd const struct qcom_clk_freq_tbl *f;
354e34a491bSAdrian Chadd const char **parent_names;
355e34a491bSAdrian Chadd uint64_t p_freq, p_clk_freq;
356e34a491bSAdrian Chadd int parent_cnt;
357e34a491bSAdrian Chadd struct clknode *p_clk;
358e34a491bSAdrian Chadd int i;
359e34a491bSAdrian Chadd
360e34a491bSAdrian Chadd sc = clknode_get_softc(clk);
361e34a491bSAdrian Chadd
362e34a491bSAdrian Chadd /*
363e34a491bSAdrian Chadd * Find a suitable frequency in the frequency table.
364e34a491bSAdrian Chadd *
365e34a491bSAdrian Chadd * TODO: should pay attention to ROUND_UP / ROUND_DOWN and add
366e34a491bSAdrian Chadd * a freqtbl method to handle both accordingly.
367e34a491bSAdrian Chadd */
368e34a491bSAdrian Chadd f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout);
369e34a491bSAdrian Chadd if (f == NULL) {
370e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
371e34a491bSAdrian Chadd "%s: no suitable freqtbl entry found for freq %llu\n",
372e34a491bSAdrian Chadd __func__,
373e34a491bSAdrian Chadd *fout);
374e34a491bSAdrian Chadd return (ERANGE);
375e34a491bSAdrian Chadd }
376e34a491bSAdrian Chadd
377e34a491bSAdrian Chadd /*
378e34a491bSAdrian Chadd * Find the parent index for the given parent clock.
379e34a491bSAdrian Chadd * Abort if we can't actually find it.
380e34a491bSAdrian Chadd *
381e34a491bSAdrian Chadd * XXX TODO: this should be a clk API call!
382e34a491bSAdrian Chadd */
383e34a491bSAdrian Chadd parent_cnt = clknode_get_parents_num(clk);
384e34a491bSAdrian Chadd parent_names = clknode_get_parent_names(clk);
385e34a491bSAdrian Chadd for (i = 0; i < parent_cnt; i++) {
386e34a491bSAdrian Chadd if (parent_names[i] == NULL)
387e34a491bSAdrian Chadd continue;
388e34a491bSAdrian Chadd if (strcmp(parent_names[i], f->parent) == 0)
389e34a491bSAdrian Chadd break;
390e34a491bSAdrian Chadd }
391e34a491bSAdrian Chadd if (i >= parent_cnt) {
392e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
393e34a491bSAdrian Chadd "%s: couldn't find suitable parent?\n",
394e34a491bSAdrian Chadd __func__);
395e34a491bSAdrian Chadd return (ENXIO);
396e34a491bSAdrian Chadd }
397e34a491bSAdrian Chadd
398e34a491bSAdrian Chadd /*
399e34a491bSAdrian Chadd * If we aren't setting the parent clock, then we need
400e34a491bSAdrian Chadd * to just program the new parent clock in and update.
401e34a491bSAdrian Chadd * (or for DRYRUN just skip that and return the new
402e34a491bSAdrian Chadd * frequency.)
403e34a491bSAdrian Chadd */
404e34a491bSAdrian Chadd if ((sc->flags & QCOM_CLK_RCG2_FLAGS_SET_RATE_PARENT) == 0) {
405e34a491bSAdrian Chadd if (flags & CLK_SET_DRYRUN) {
406e34a491bSAdrian Chadd *fout = f->freq;
407e34a491bSAdrian Chadd return (0);
408e34a491bSAdrian Chadd }
409e34a491bSAdrian Chadd
410e34a491bSAdrian Chadd if (sc->safe_pre_parent_idx > -1) {
411e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
412e34a491bSAdrian Chadd "%s: setting to safe parent idx %d\n",
413e34a491bSAdrian Chadd __func__,
414e34a491bSAdrian Chadd sc->safe_pre_parent_idx);
415e34a491bSAdrian Chadd CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
416e34a491bSAdrian Chadd qcom_clk_rcg2_set_parent_index_locked(sc,
417e34a491bSAdrian Chadd sc->safe_pre_parent_idx);
418e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
419e34a491bSAdrian Chadd "%s: safe parent: updating config\n", __func__);
420e34a491bSAdrian Chadd if (! qcom_clk_rcg2_update_config_locked(sc)) {
421e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
422e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
423e34a491bSAdrian Chadd "%s: error updating config\n",
424e34a491bSAdrian Chadd __func__);
425e34a491bSAdrian Chadd return (ENXIO);
426e34a491bSAdrian Chadd }
427e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
428e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
429e34a491bSAdrian Chadd "%s: safe parent: done\n", __func__);
430e34a491bSAdrian Chadd clknode_set_parent_by_idx(sc->clknode,
431e34a491bSAdrian Chadd sc->safe_pre_parent_idx);
432e34a491bSAdrian Chadd }
433e34a491bSAdrian Chadd /* Program parent index, then schedule update */
434e34a491bSAdrian Chadd CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
435e34a491bSAdrian Chadd qcom_clk_rcg2_set_parent_index_locked(sc, i);
436e34a491bSAdrian Chadd if (! qcom_clk_rcg2_update_config_locked(sc)) {
437e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
438e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
439e34a491bSAdrian Chadd "%s: couldn't program in parent idx %u!\n",
440e34a491bSAdrian Chadd __func__, i);
441e34a491bSAdrian Chadd return (ENXIO);
442e34a491bSAdrian Chadd }
443e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
444e34a491bSAdrian Chadd clknode_set_parent_by_idx(sc->clknode, i);
445e34a491bSAdrian Chadd *fout = f->freq;
446e34a491bSAdrian Chadd return (0);
447e34a491bSAdrian Chadd }
448e34a491bSAdrian Chadd
449e34a491bSAdrian Chadd /*
450e34a491bSAdrian Chadd * If we /are/ setting the parent clock, then we need
451e34a491bSAdrian Chadd * to determine what frequency we need the parent to
452e34a491bSAdrian Chadd * be, and then reconfigure the parent to the new
453e34a491bSAdrian Chadd * frequency, and then change our parent.
454e34a491bSAdrian Chadd *
455e34a491bSAdrian Chadd * (Again, if we're doing DRYRUN, just skip that
456e34a491bSAdrian Chadd * and return the new frequency.)
457e34a491bSAdrian Chadd */
458e34a491bSAdrian Chadd p_clk = clknode_find_by_name(f->parent);
459e34a491bSAdrian Chadd if (p_clk == NULL) {
460e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
461e34a491bSAdrian Chadd "%s: couldn't find parent clk (%s)\n",
462e34a491bSAdrian Chadd __func__, f->parent);
463e34a491bSAdrian Chadd return (ENXIO);
464e34a491bSAdrian Chadd }
465e34a491bSAdrian Chadd
466e34a491bSAdrian Chadd /*
467e34a491bSAdrian Chadd * Calculate required frequency from said parent clock to
468e34a491bSAdrian Chadd * meet the needs of our target clock.
469e34a491bSAdrian Chadd */
470e34a491bSAdrian Chadd p_freq = qcom_clk_rcg2_calc_input_freq(f->freq, f->m, f->n,
471e34a491bSAdrian Chadd f->pre_div);
472e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
473e34a491bSAdrian Chadd "%s: request %llu, parent %s freq %llu, parent freq %llu\n",
474e34a491bSAdrian Chadd __func__,
475e34a491bSAdrian Chadd *fout,
476e34a491bSAdrian Chadd f->parent,
477e34a491bSAdrian Chadd f->freq,
478e34a491bSAdrian Chadd p_freq);
479e34a491bSAdrian Chadd
480e34a491bSAdrian Chadd /*
481e34a491bSAdrian Chadd * To ensure glitch-free operation on some clocks, set it to
482e34a491bSAdrian Chadd * a safe parent before programming our divisor and the parent
483e34a491bSAdrian Chadd * clock configuration. Then once it's done, flip the parent
484e34a491bSAdrian Chadd * to the new parent.
485e34a491bSAdrian Chadd *
486e34a491bSAdrian Chadd * If we're doing a dry-run then we don't need to re-parent the
487e34a491bSAdrian Chadd * clock just yet!
488e34a491bSAdrian Chadd */
489e34a491bSAdrian Chadd if (((flags & CLK_SET_DRYRUN) == 0) &&
490e34a491bSAdrian Chadd (sc->safe_pre_parent_idx > -1)) {
491e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
492e34a491bSAdrian Chadd "%s: setting to safe parent idx %d\n",
493e34a491bSAdrian Chadd __func__,
494e34a491bSAdrian Chadd sc->safe_pre_parent_idx);
495e34a491bSAdrian Chadd CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
496e34a491bSAdrian Chadd qcom_clk_rcg2_set_parent_index_locked(sc,
497e34a491bSAdrian Chadd sc->safe_pre_parent_idx);
498e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
499e34a491bSAdrian Chadd "%s: safe parent: updating config\n", __func__);
500e34a491bSAdrian Chadd if (! qcom_clk_rcg2_update_config_locked(sc)) {
501e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
502e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
503e34a491bSAdrian Chadd "%s: error updating config\n",
504e34a491bSAdrian Chadd __func__);
505e34a491bSAdrian Chadd return (ENXIO);
506e34a491bSAdrian Chadd }
507e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
508e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
509e34a491bSAdrian Chadd "%s: safe parent: done\n", __func__);
510e34a491bSAdrian Chadd clknode_set_parent_by_idx(sc->clknode,
511e34a491bSAdrian Chadd sc->safe_pre_parent_idx);
512e34a491bSAdrian Chadd }
513e34a491bSAdrian Chadd
514e34a491bSAdrian Chadd /*
515e34a491bSAdrian Chadd * Set the parent frequency before we change our mux and divisor
516e34a491bSAdrian Chadd * configuration.
517e34a491bSAdrian Chadd */
518e34a491bSAdrian Chadd if (clknode_get_freq(p_clk, &p_clk_freq) != 0) {
519e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
520e34a491bSAdrian Chadd "%s: couldn't get freq for parent clock %s\n",
521e34a491bSAdrian Chadd __func__,
522e34a491bSAdrian Chadd f->parent);
523e34a491bSAdrian Chadd return (ENXIO);
524e34a491bSAdrian Chadd }
525e34a491bSAdrian Chadd if (p_clk_freq != p_freq) {
526e34a491bSAdrian Chadd uint64_t n_freq;
527e34a491bSAdrian Chadd int rv;
528e34a491bSAdrian Chadd
529e34a491bSAdrian Chadd /*
530e34a491bSAdrian Chadd * If we're doing a dryrun then call test_freq() not set_freq().
531e34a491bSAdrian Chadd * That way we get the frequency back that we would be set to.
532e34a491bSAdrian Chadd *
533e34a491bSAdrian Chadd * If we're not doing a dry run then set the frequency, then
534e34a491bSAdrian Chadd * call get_freq to get what it was set to.
535e34a491bSAdrian Chadd */
536e34a491bSAdrian Chadd if (flags & CLK_SET_DRYRUN) {
537e34a491bSAdrian Chadd n_freq = p_freq;
538e34a491bSAdrian Chadd rv = clknode_test_freq(p_clk, n_freq, flags, 0,
539e34a491bSAdrian Chadd &p_freq);
540e34a491bSAdrian Chadd } else {
541e34a491bSAdrian Chadd rv = clknode_set_freq(p_clk, p_freq, flags, 0);
542e34a491bSAdrian Chadd }
543e34a491bSAdrian Chadd
544e34a491bSAdrian Chadd if (rv != 0) {
545e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
546e34a491bSAdrian Chadd "%s: couldn't set parent clock %s frequency to "
547e34a491bSAdrian Chadd "%llu\n",
548e34a491bSAdrian Chadd __func__,
549e34a491bSAdrian Chadd f->parent,
550e34a491bSAdrian Chadd p_freq);
551e34a491bSAdrian Chadd return (ENXIO);
552e34a491bSAdrian Chadd }
553e34a491bSAdrian Chadd
554e34a491bSAdrian Chadd /* Frequency was set, fetch what it was set to */
555e34a491bSAdrian Chadd if ((flags & CLK_SET_DRYRUN) == 0) {
556e34a491bSAdrian Chadd rv = clknode_get_freq(p_clk, &p_freq);
557e34a491bSAdrian Chadd if (rv != 0) {
558e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
559e34a491bSAdrian Chadd "%s: couldn't get parent frequency",
560e34a491bSAdrian Chadd __func__);
561e34a491bSAdrian Chadd return (ENXIO);
562e34a491bSAdrian Chadd }
563e34a491bSAdrian Chadd }
564e34a491bSAdrian Chadd }
565e34a491bSAdrian Chadd
566e34a491bSAdrian Chadd DPRINTF(clknode_get_device(sc->clknode),
56797a20be0SGordon Bergling "%s: requested freq=%llu, target freq=%llu,"
568e34a491bSAdrian Chadd " parent choice=%s, parent_freq=%llu\n",
569e34a491bSAdrian Chadd __func__,
570e34a491bSAdrian Chadd *fout,
571e34a491bSAdrian Chadd f->freq,
572e34a491bSAdrian Chadd f->parent,
573e34a491bSAdrian Chadd p_freq);
574e34a491bSAdrian Chadd
575e34a491bSAdrian Chadd /*
576e34a491bSAdrian Chadd * Set the parent node, the parent programming and the divisor
577e34a491bSAdrian Chadd * config. Because they're done together, we don't go via
578e34a491bSAdrian Chadd * a mux method on this node.
579e34a491bSAdrian Chadd */
580e34a491bSAdrian Chadd
581e34a491bSAdrian Chadd /*
582e34a491bSAdrian Chadd * Program the divisor and parent.
583e34a491bSAdrian Chadd */
584e34a491bSAdrian Chadd if ((flags & CLK_SET_DRYRUN) == 0) {
585e34a491bSAdrian Chadd CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
586e34a491bSAdrian Chadd qcom_clk_rcg2_set_config_locked(sc, f, i);
587e34a491bSAdrian Chadd if (! qcom_clk_rcg2_update_config_locked(sc)) {
588e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
589e34a491bSAdrian Chadd device_printf(clknode_get_device(sc->clknode),
590e34a491bSAdrian Chadd "%s: couldn't program in divisor, help!\n",
591e34a491bSAdrian Chadd __func__);
592e34a491bSAdrian Chadd return (ENXIO);
593e34a491bSAdrian Chadd }
594e34a491bSAdrian Chadd CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
595e34a491bSAdrian Chadd clknode_set_parent_by_idx(sc->clknode, i);
596e34a491bSAdrian Chadd }
597e34a491bSAdrian Chadd
598e34a491bSAdrian Chadd /*
599e34a491bSAdrian Chadd * p_freq is now the frequency that the parent /is/ set to.
600e34a491bSAdrian Chadd * (Or would be set to for a dry run.)
601e34a491bSAdrian Chadd *
602e34a491bSAdrian Chadd * Calculate what the eventual frequency would be, we'll want
603e34a491bSAdrian Chadd * this to return when we're done - and again, if it's a dryrun,
604e34a491bSAdrian Chadd * don't set anything up. This doesn't rely on the register
605e34a491bSAdrian Chadd * contents.
606e34a491bSAdrian Chadd */
607e34a491bSAdrian Chadd *fout = qcom_clk_rcg2_calc_rate(p_freq, (f->n == 0 ? 0 : 1),
608e34a491bSAdrian Chadd f->m, f->n, f->pre_div);
609e34a491bSAdrian Chadd
610e34a491bSAdrian Chadd return (0);
611e34a491bSAdrian Chadd }
612e34a491bSAdrian Chadd
613e34a491bSAdrian Chadd static clknode_method_t qcom_clk_rcg2_methods[] = {
614e34a491bSAdrian Chadd /* Device interface */
615e34a491bSAdrian Chadd CLKNODEMETHOD(clknode_init, qcom_clk_rcg2_init),
616e34a491bSAdrian Chadd CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_rcg2_recalc),
617e34a491bSAdrian Chadd CLKNODEMETHOD(clknode_set_gate, qcom_clk_rcg2_set_gate),
618e34a491bSAdrian Chadd CLKNODEMETHOD(clknode_set_freq, qcom_clk_rcg2_set_freq),
619e34a491bSAdrian Chadd CLKNODEMETHOD_END
620e34a491bSAdrian Chadd };
621e34a491bSAdrian Chadd
622e34a491bSAdrian Chadd DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_rcg2_class, qcom_clk_rcg2_methods,
623e34a491bSAdrian Chadd sizeof(struct qcom_clk_rcg2_sc), clknode_class);
624e34a491bSAdrian Chadd
625e34a491bSAdrian Chadd int
qcom_clk_rcg2_register(struct clkdom * clkdom,struct qcom_clk_rcg2_def * clkdef)626e34a491bSAdrian Chadd qcom_clk_rcg2_register(struct clkdom *clkdom,
627e34a491bSAdrian Chadd struct qcom_clk_rcg2_def *clkdef)
628e34a491bSAdrian Chadd {
629e34a491bSAdrian Chadd struct clknode *clk;
630e34a491bSAdrian Chadd struct qcom_clk_rcg2_sc *sc;
631e34a491bSAdrian Chadd
632e34a491bSAdrian Chadd /*
633e34a491bSAdrian Chadd * Right now the rcg2 code isn't supporting turning off the clock
634e34a491bSAdrian Chadd * or limiting it to the lowest parent clock. But, do set the
635e34a491bSAdrian Chadd * flags appropriately.
636e34a491bSAdrian Chadd */
637e34a491bSAdrian Chadd if (clkdef->flags & QCOM_CLK_RCG2_FLAGS_CRITICAL)
638e34a491bSAdrian Chadd clkdef->clkdef.flags |= CLK_NODE_CANNOT_STOP;
639e34a491bSAdrian Chadd
640e34a491bSAdrian Chadd clk = clknode_create(clkdom, &qcom_clk_rcg2_class, &clkdef->clkdef);
641e34a491bSAdrian Chadd if (clk == NULL)
642e34a491bSAdrian Chadd return (1);
643e34a491bSAdrian Chadd
644e34a491bSAdrian Chadd sc = clknode_get_softc(clk);
645e34a491bSAdrian Chadd sc->clknode = clk;
646e34a491bSAdrian Chadd
647e34a491bSAdrian Chadd sc->cmd_rcgr = clkdef->cmd_rcgr;
648e34a491bSAdrian Chadd sc->hid_width = clkdef->hid_width;
649e34a491bSAdrian Chadd sc->mnd_width = clkdef->mnd_width;
650e34a491bSAdrian Chadd sc->safe_src_idx = clkdef->safe_src_idx;
651e34a491bSAdrian Chadd sc->safe_pre_parent_idx = clkdef->safe_pre_parent_idx;
652e34a491bSAdrian Chadd sc->cfg_offset = clkdef->cfg_offset;
653e34a491bSAdrian Chadd sc->flags = clkdef->flags;
654e34a491bSAdrian Chadd sc->freq_tbl = clkdef->freq_tbl;
655e34a491bSAdrian Chadd
656e34a491bSAdrian Chadd clknode_register(clkdom, clk);
657e34a491bSAdrian Chadd
658e34a491bSAdrian Chadd return (0);
659e34a491bSAdrian Chadd }
660