Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F137807270
D33665.id54425.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
70 KB
Referenced Files
None
Subscribers
None
D33665.id54425.diff
View Options
diff --git a/sys/arm/qualcomm/std.ipq4018 b/sys/arm/qualcomm/std.ipq4018
--- a/sys/arm/qualcomm/std.ipq4018
+++ b/sys/arm/qualcomm/std.ipq4018
@@ -8,6 +8,14 @@
arm/qualcomm/qcom_gcc_ipq4018_reset.c optional qcom_gcc_ipq4018
arm/qualcomm/qcom_gcc_ipq4018_clock.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_fepll.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_fdiv.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_apssdiv.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_freqtbl.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_rcg2.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_branch2.c optional qcom_gcc_ipq4018
+dev/qcom_clk/qcom_clk_ro_div.c optional qcom_gcc_ipq4018
+
dev/qcom_tlmm/qcom_tlmm_debug.c optional qcom_tlmm_ipq4018
dev/qcom_tlmm/qcom_tlmm_ipq4018.c optional qcom_tlmm_ipq4018
dev/qcom_tlmm/qcom_tlmm_ipq4018_hw.c optional qcom_tlmm_ipq4018
diff --git a/sys/dev/qcom_clk/qcom_clk_apssdiv.h b/sys/dev/qcom_clk/qcom_clk_apssdiv.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_apssdiv.h
@@ -0,0 +1,48 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_APSS_H__
+#define __QCOM_CLK_APSS_H__
+
+#include "qcom_clk_freqtbl.h"
+
+struct qcom_clk_apssdiv_def {
+ struct clknode_init_def clkdef;
+ uint32_t div_offset;
+ uint32_t div_width;
+ uint32_t div_shift;
+ uint32_t enable_offset;
+ uint32_t enable_shift;
+ const struct qcom_clk_freq_tbl *freq_tbl;
+};
+
+extern int qcom_clk_apssdiv_register(struct clkdom *clkdom,
+ struct qcom_clk_apssdiv_def *clkdef);
+
+#endif /* __QCOM_CLK_APSS_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_apssdiv.c b/sys/dev/qcom_clk/qcom_clk_apssdiv.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_apssdiv.c
@@ -0,0 +1,287 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/clk/clk_div.h>
+#include <dev/extres/clk/clk_fixed.h>
+#include <dev/extres/clk/clk_mux.h>
+
+#include "qcom_clk_freqtbl.h"
+#include "qcom_clk_apssdiv.h"
+
+#include "clkdev_if.h"
+
+/*
+ * This is a combination gate, divisor/PLL configuration
+ * for the APSS CPU clock.
+ */
+
+#if 0
+#define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
+#else
+#define DPRINTF(dev, msg...)
+#endif
+
+struct qcom_clk_apssdiv_sc {
+ struct clknode *clknode;
+ uint32_t div_offset;
+ uint32_t div_width;
+ uint32_t div_shift;
+ uint32_t enable_offset;
+ uint32_t enable_shift;
+ const struct qcom_clk_freq_tbl *freq_tbl;
+};
+
+static uint64_t
+qcom_clk_apssdiv_calc_rate(struct clknode *clk, uint64_t freq, uint32_t cdiv)
+{
+ uint32_t pre_div;
+
+ /*
+ * The divisor isn't a linear map with a linear pre-divisor.
+ */
+ if (cdiv > 10) {
+ pre_div = (cdiv + 1) * 2;
+ } else {
+ pre_div = cdiv + 12;
+ }
+ /*
+ * Multiplier is a fixed "2" here.
+ */
+ return (freq * 2L) / pre_div;
+}
+
+static int
+qcom_clk_apssdiv_recalc(struct clknode *clk, uint64_t *freq)
+{
+ struct qcom_clk_apssdiv_sc *sc;
+ uint32_t reg, cdiv;
+
+ sc = clknode_get_softc(clk);
+
+ if (freq == NULL || *freq == 0) {
+ printf("%s: called; NULL or 0 frequency\n", __func__);
+ return (ENXIO);
+ }
+
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®);
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ cdiv = (reg >> sc->div_shift) & ((1U << sc->div_width) - 1);
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: called; cdiv=0x%x, freq=%llu\n", __func__, cdiv, *freq);
+
+ *freq = qcom_clk_apssdiv_calc_rate(clk, *freq, cdiv);
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: called; freq is %llu\n", __func__, *freq);
+ return (0);
+}
+
+#if 0
+static bool
+qcom_clk_apssdiv_get_gate_locked(struct qcom_clk_apssdiv_sc *sc)
+{
+ uint32_t reg;
+
+ if (sc->enable_offset == 0)
+ return (false);
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
+ ®);
+
+ return (!! (reg & (1U << sc->enable_shift)));
+}
+#endif
+
+static int
+qcom_clk_apssdiv_init(struct clknode *clk, device_t dev)
+{
+ struct qcom_clk_apssdiv_sc *sc;
+
+ sc = clknode_get_softc(clk);
+
+ /*
+ * There's only a single parent here for an fixed divisor,
+ * so just set it to 0; the caller doesn't need to supply it.
+ *
+ * Note that the freqtbl entries have an upstream clock,
+ * but the APSS div/gate only has a single upstream and we
+ * don't program anything else specific in here.
+ */
+ clknode_init_parent_idx(clk, 0);
+
+ return (0);
+}
+
+static int
+qcom_clk_apssdiv_set_gate(struct clknode *clk, bool enable)
+{
+ struct qcom_clk_apssdiv_sc *sc;
+ uint32_t reg;
+
+ sc = clknode_get_softc(clk);
+
+ if (sc->enable_offset == 0) {
+ return (ENXIO);
+ }
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: called; enable=%d\n", __func__, enable);
+
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
+ ®);
+ if (enable) {
+ reg |= (1U << sc->enable_shift);
+ } else {
+ reg &= ~(1U << sc->enable_shift);
+ }
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset,
+ reg);
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ return (0);
+}
+
+/*
+ * Set frequency
+ *
+ * fin - the parent frequency, if exists
+ * fout - starts as the requested frequency, ends with the configured
+ * or dry-run frequency
+ * Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN
+ * retval - 0, ERANGE
+ */
+static int
+qcom_clk_apssdiv_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
+ int flags, int *stop)
+{
+ const struct qcom_clk_freq_tbl *f;
+ struct qcom_clk_apssdiv_sc *sc;
+ uint64_t f_freq;
+ uint32_t reg;
+
+ sc = clknode_get_softc(clk);
+
+ /* There are no further PLLs to set in this chain */
+ *stop = 1;
+
+ /* Search the table for a suitable frequency */
+ f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout);
+ if (f == NULL) {
+ return (ERANGE);
+ }
+
+ /*
+ * Calculate what the resultant frequency would be based on the
+ * parent PLL.
+ */
+ f_freq = qcom_clk_apssdiv_calc_rate(clk, fin, f->pre_div);
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: dryrun: %d, fin=%llu fout=%llu f_freq=%llu pre_div=%u"
+ " target_freq=%llu\n",
+ __func__,
+ !! (flags & CLK_SET_DRYRUN),
+ fin, *fout, f_freq, f->pre_div, f->freq);
+
+ if (flags & CLK_SET_DRYRUN) {
+ *fout = f_freq;
+ return (0);
+ }
+
+ /*
+ * Program in the new pre-divisor.
+ */
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->div_offset, ®);
+ reg &= ~(((1U << sc->div_width) - 1) << sc->div_shift);
+ reg |= (f->pre_div << sc->div_shift);
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->div_offset, reg);
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ /*
+ * The linux driver notes there's no status/completion bit to poll.
+ * So sleep for a bit and hope that's enough time for it to
+ * settle.
+ */
+ DELAY(1);
+
+ *fout = f_freq;
+
+ return (0);
+}
+
+static clknode_method_t qcom_clk_apssdiv_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, qcom_clk_apssdiv_init),
+ CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_apssdiv_recalc),
+ CLKNODEMETHOD(clknode_set_gate, qcom_clk_apssdiv_set_gate),
+ CLKNODEMETHOD(clknode_set_freq, qcom_clk_apssdiv_set_freq),
+ CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(qcom_clk_apssdiv, qcom_clk_apssdiv_class,
+ qcom_clk_apssdiv_methods, sizeof(struct qcom_clk_apssdiv_sc),
+ clknode_class);
+
+int
+qcom_clk_apssdiv_register(struct clkdom *clkdom,
+ struct qcom_clk_apssdiv_def *clkdef)
+{
+ struct clknode *clk;
+ struct qcom_clk_apssdiv_sc *sc;
+
+ clk = clknode_create(clkdom, &qcom_clk_apssdiv_class, &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clknode = clk;
+
+ sc->div_offset = clkdef->div_offset;
+ sc->div_width = clkdef->div_width;
+ sc->div_shift = clkdef->div_shift;
+ sc->freq_tbl = clkdef->freq_tbl;
+ sc->enable_offset = clkdef->enable_offset;
+ sc->enable_shift = clkdef->enable_shift;
+
+ clknode_register(clkdom, clk);
+
+ return (0);
+}
diff --git a/sys/dev/qcom_clk/qcom_clk_branch2.h b/sys/dev/qcom_clk/qcom_clk_branch2.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_branch2.h
@@ -0,0 +1,70 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_BRANCH2_H__
+#define __QCOM_CLK_BRANCH2_H__
+
+#include "qcom_clk_freqtbl.h"
+
+/* halt is 1 */
+#define QCOM_CLK_BRANCH2_BRANCH_HALT 0
+
+/* halt is inverted (ie, 0) */
+#define QCOM_CLK_BRANCH2_BRANCH_HALT_INVERTED 1
+
+/* Don't check the bit, just delay */
+#define QCOM_CLK_BRANCH2_BRANCH_HALT_DELAY 2
+
+/* Don't check the halt bit at all */
+#define QCOM_CLK_BRANCH2_BRANCH_HALT_SKIP 3
+
+/* Flags */
+#define QCOM_CLK_BRANCH2_FLAGS_CRITICAL 0x1
+#define QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT 0x2
+
+struct qcom_clk_branch2_def {
+ struct clknode_init_def clkdef;
+
+ uint32_t flags;
+
+ uint32_t enable_offset; /* enable register*/
+ uint32_t enable_shift; /* enable bit shift */
+
+ uint32_t hwcg_reg; /* hw clock gate register */
+ uint32_t hwcg_bit;
+ uint32_t halt_reg; /* halt register */
+
+ uint32_t halt_check_type;
+ bool halt_check_voted; /* whether to delay when waiting */
+};
+
+extern int qcom_clk_branch2_register(struct clkdom *clkdom,
+ struct qcom_clk_branch2_def *clkdef);
+
+#endif /* __QCOM_CLK_BRANCH2_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_branch2.c b/sys/dev/qcom_clk/qcom_clk_branch2.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_branch2.c
@@ -0,0 +1,290 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/clk/clk_div.h>
+#include <dev/extres/clk/clk_fixed.h>
+#include <dev/extres/clk/clk_mux.h>
+
+#include "qcom_clk_branch2.h"
+#include "qcom_clk_branch2_reg.h"
+
+#include "clkdev_if.h"
+
+/*
+ * This is a combination gate/status and dynamic hardware clock gating with
+ * voting.
+ */
+
+#if 0
+#define DPRINTF(dev, msg...) device_printf(dev, msg);
+#else
+#define DPRINTF(dev, msg...)
+#endif
+
+struct qcom_clk_branch2_sc {
+ struct clknode *clknode;
+ uint32_t flags;
+ uint32_t enable_offset;
+ uint32_t enable_shift;
+ uint32_t hwcg_reg;
+ uint32_t hwcg_bit;
+ uint32_t halt_reg;
+ uint32_t halt_check_type;
+ bool halt_check_voted;
+};
+#if 0
+static bool
+qcom_clk_branch2_get_gate_locked(struct qcom_clk_branch2_sc *sc)
+{
+ uint32_t reg;
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
+ ®);
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: offset=0x%x, reg=0x%x\n", __func__,
+ sc->enable_offset, reg);
+
+ return (!! (reg & (1U << sc->enable_shift)));
+}
+#endif
+
+static int
+qcom_clk_branch2_init(struct clknode *clk, device_t dev)
+{
+
+ clknode_init_parent_idx(clk, 0);
+
+ return (0);
+}
+
+static bool
+qcom_clk_branch2_in_hwcg_mode_locked(struct qcom_clk_branch2_sc *sc)
+{
+ uint32_t reg;
+
+ if (sc->hwcg_reg == 0)
+ return (false);
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->hwcg_reg,
+ ®);
+
+ return (!! (reg & (1U << sc->hwcg_bit)));
+}
+
+static bool
+qcom_clk_branch2_check_halt_locked(struct qcom_clk_branch2_sc *sc, bool enable)
+{
+ uint32_t reg;
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->halt_reg, ®);
+
+ if (enable) {
+ /*
+ * The upstream Linux code is .. unfortunate.
+ *
+ * Here it says "return true if BRANCH_CLK_OFF is not set,
+ * or if the status field = FSM_STATUS_ON AND
+ * the clk_off field is 0.
+ *
+ * Which .. is weird, because I can't currently see
+ * how we'd ever need to check FSM_STATUS_ON - the only
+ * valid check for the FSM status also requires clk_off=0.
+ */
+ return !! ((reg & QCOM_CLK_BRANCH2_CLK_OFF) == 0);
+ } else {
+ return !! (reg & QCOM_CLK_BRANCH2_CLK_OFF);
+ }
+}
+
+/*
+ * Check if the given type/voted flag match what is configured.
+ */
+static bool
+qcom_clk_branch2_halt_check_type(struct qcom_clk_branch2_sc *sc,
+ uint32_t type, bool voted)
+{
+ return ((sc->halt_check_type == type) &&
+ (sc->halt_check_voted == voted));
+}
+
+static bool
+qcom_clk_branch2_wait_locked(struct qcom_clk_branch2_sc *sc, bool enable)
+{
+
+ if (qcom_clk_branch2_halt_check_type(sc,
+ QCOM_CLK_BRANCH2_BRANCH_HALT_SKIP, false))
+ return (true);
+ if (qcom_clk_branch2_in_hwcg_mode_locked(sc))
+ return (true);
+
+ if ((qcom_clk_branch2_halt_check_type(sc,
+ QCOM_CLK_BRANCH2_BRANCH_HALT_DELAY, false)) ||
+ (enable == false && sc->halt_check_voted)) {
+ DELAY(10);
+ return (true);
+ }
+
+ if ((qcom_clk_branch2_halt_check_type(sc,
+ QCOM_CLK_BRANCH2_BRANCH_HALT_INVERTED, false)) ||
+ (qcom_clk_branch2_halt_check_type(sc,
+ QCOM_CLK_BRANCH2_BRANCH_HALT, false)) ||
+ (enable && sc->halt_check_voted)) {
+ int count;
+
+ for (count = 0; count < 200; count++) {
+ if (qcom_clk_branch2_check_halt_locked(sc, enable))
+ return (true);
+ DELAY(1);
+ }
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: enable stuck (%d)!\n", __func__, enable);
+ return (false);
+ }
+
+ /* Default */
+ return (true);
+}
+
+static int
+qcom_clk_branch2_set_gate(struct clknode *clk, bool enable)
+{
+ struct qcom_clk_branch2_sc *sc;
+ uint32_t reg;
+
+ sc = clknode_get_softc(clk);
+
+ DPRINTF(clknode_get_device(sc->clknode), "%s: called\n", __func__);
+
+ if (sc->enable_offset == 0) {
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: no enable_offset", __func__);
+ return (ENXIO);
+ }
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: called; enable=%d\n", __func__, enable);
+
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->enable_offset,
+ ®);
+ if (enable) {
+ reg |= (1U << sc->enable_shift);
+ } else {
+ reg &= ~(1U << sc->enable_shift);
+ }
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode), sc->enable_offset,
+ reg);
+
+ /*
+ * Now wait for the clock branch to update!
+ */
+ if (! qcom_clk_branch2_wait_locked(sc, enable)) {
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: failed to wait!\n", __func__);
+ return (ENXIO);
+ }
+
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ return (0);
+}
+
+static int
+qcom_clk_branch2_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
+ int flags, int *stop)
+{
+ struct qcom_clk_branch2_sc *sc;
+
+ sc = clknode_get_softc(clk);
+
+ /* We only support what our parent clock is currently set as */
+ *fout = fin;
+
+ /* .. and stop here if we don't have SET_RATE_PARENT */
+ if (sc->flags & QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT)
+ *stop = 0;
+ else
+ *stop = 1;
+ return (0);
+}
+
+
+static clknode_method_t qcom_clk_branch2_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, qcom_clk_branch2_init),
+ CLKNODEMETHOD(clknode_set_gate, qcom_clk_branch2_set_gate),
+ CLKNODEMETHOD(clknode_set_freq, qcom_clk_branch2_set_freq),
+ CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(qcom_clk_branch2, qcom_clk_branch2_class,
+ qcom_clk_branch2_methods, sizeof(struct qcom_clk_branch2_sc),
+ clknode_class);
+
+int
+qcom_clk_branch2_register(struct clkdom *clkdom,
+ struct qcom_clk_branch2_def *clkdef)
+{
+ struct clknode *clk;
+ struct qcom_clk_branch2_sc *sc;
+
+ if (clkdef->flags & QCOM_CLK_BRANCH2_FLAGS_CRITICAL)
+ clkdef->clkdef.flags |= CLK_NODE_CANNOT_STOP;
+
+ clk = clknode_create(clkdom, &qcom_clk_branch2_class,
+ &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clknode = clk;
+
+ sc->enable_offset = clkdef->enable_offset;
+ sc->enable_shift = clkdef->enable_shift;
+ sc->halt_reg = clkdef->halt_reg;
+ sc->hwcg_reg = clkdef->hwcg_reg;
+ sc->hwcg_bit = clkdef->hwcg_bit;
+ sc->halt_check_type = clkdef->halt_check_type;
+ sc->halt_check_voted = clkdef->halt_check_voted;
+ sc->flags = clkdef->flags;
+
+ clknode_register(clkdom, clk);
+
+ return (0);
+}
diff --git a/sys/dev/qcom_clk/qcom_clk_branch2_reg.h b/sys/dev/qcom_clk/qcom_clk_branch2_reg.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_branch2_reg.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_BRANCH2_REG_H__
+#define __QCOM_CLK_BRANCH2_REG_H__
+
+#define QCOM_CLK_BRANCH2_CLK_OFF (1U << 31)
+#define QCOM_CLK_BRANCH2_NOC_FSM_STATUS_SHIFT 28
+#define QCOM_CLK_BRANCH2_NOC_FSM_STATUS_MASK 0x7
+#define QCOM_CLK_BRANCH2_NOC_FSM_STATUS_ON \
+ (0x2 << QCOM_CLK_BRANCH2_NOC_FSM_STATUS_SHIFT)
+
+#endif /* __QCOM_CLK_BRANCH2_REG_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_fdiv.h b/sys/dev/qcom_clk/qcom_clk_fdiv.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_fdiv.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_FDIV_H__
+#define __QCOM_CLK_FDIV_H__
+
+struct qcom_clk_fdiv_def {
+ struct clknode_init_def clkdef;
+ uint32_t divisor; /* Fixed divisor */
+};
+
+extern int qcom_clk_fdiv_register(struct clkdom *clkdom,
+ struct qcom_clk_fdiv_def *clkdef);
+
+#endif /* __QCOM_CLK_FDIV_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_fdiv.c b/sys/dev/qcom_clk/qcom_clk_fdiv.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_fdiv.c
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/clk/clk_div.h>
+#include <dev/extres/clk/clk_fixed.h>
+#include <dev/extres/clk/clk_mux.h>
+
+#include "qcom_clk_fdiv.h"
+
+#include "clkdev_if.h"
+
+/*
+ * This is a fixed divisor node. It represents some divisor
+ * that is setup by the boot environment and we don't have
+ * any need for the driver to go and fiddle with.
+ *
+ * It likely should just live in the extres/clk code.
+ */
+
+struct qcom_clk_fdiv_sc {
+ struct clknode *clknode;
+ uint32_t divisor;
+};
+
+static int
+qcom_clk_fdiv_recalc(struct clknode *clk, uint64_t *freq)
+{
+ struct qcom_clk_fdiv_sc *sc;
+
+ sc = clknode_get_softc(clk);
+
+ if (freq == NULL || *freq == 0) {
+ printf("%s: called; NULL or 0 frequency\n", __func__);
+ return (ENXIO);
+ }
+
+ *freq = *freq / sc->divisor;
+ return (0);
+}
+
+static int
+qcom_clk_fdiv_init(struct clknode *clk, device_t dev)
+{
+ /*
+ * There's only a single parent here for an fixed divisor,
+ * so just set it to 0; the caller doesn't need to supply it.
+ */
+ clknode_init_parent_idx(clk, 0);
+
+ return(0);
+}
+
+static clknode_method_t qcom_clk_fdiv_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, qcom_clk_fdiv_init),
+ CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_fdiv_recalc),
+ CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_fdiv_class, qcom_clk_fdiv_methods,
+ sizeof(struct qcom_clk_fdiv_sc), clknode_class);
+
+int
+qcom_clk_fdiv_register(struct clkdom *clkdom, struct qcom_clk_fdiv_def *clkdef)
+{
+ struct clknode *clk;
+ struct qcom_clk_fdiv_sc *sc;
+
+ clk = clknode_create(clkdom, &qcom_clk_fdiv_class, &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clknode = clk;
+
+ sc->divisor = clkdef->divisor;
+
+ clknode_register(clkdom, clk);
+
+ return (0);
+}
diff --git a/sys/dev/qcom_clk/qcom_clk_fepll.h b/sys/dev/qcom_clk/qcom_clk_fepll.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_fepll.h
@@ -0,0 +1,45 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_FEPLL_H__
+#define __QCOM_CLK_FEPLL_H__
+
+struct qcom_clk_fepll_def {
+ struct clknode_init_def clkdef;
+ uint32_t offset; /* Register offset */
+ uint32_t fdbkdiv_shift; /* FDBKDIV base */
+ uint32_t fdbkdiv_width; /* FDBKDIV width */
+ uint32_t refclkdiv_shift; /* REFCLKDIV base */
+ uint32_t refclkdiv_width; /* REFCLKDIV width */
+};
+
+extern int qcom_clk_fepll_register(struct clkdom *clkdom,
+ struct qcom_clk_fepll_def *clkdef);
+
+#endif /* __QCOM_CLK_FEPLL_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_fepll.c b/sys/dev/qcom_clk/qcom_clk_fepll.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_fepll.c
@@ -0,0 +1,153 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/clk/clk_div.h>
+#include <dev/extres/clk/clk_fixed.h>
+#include <dev/extres/clk/clk_mux.h>
+
+#include "qcom_clk_fepll.h"
+
+#include "clkdev_if.h"
+
+#if 0
+#define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
+#else
+#define DPRINTF(dev, msg...)
+#endif
+
+/*
+ * This is the top-level PLL clock on the IPQ4018/IPQ4019.
+ * It's a fixed PLL clock that feeds a bunch of divisors into
+ * downstrem FEPLL* and DDR clocks.
+ *
+ * Now, on Linux the clock code creates multiple instances of this
+ * with an inbuilt divisor. Here instead there'll be a single
+ * instance of the FEPLL, and then normal divisors will feed into
+ * the multiple PLL nodes.
+ */
+
+struct qcom_clk_fepll_sc {
+ struct clknode *clknode;
+ uint32_t offset;
+ uint32_t fdbkdiv_shift; /* FDBKDIV base */
+ uint32_t fdbkdiv_width; /* FDBKDIV width */
+ uint32_t refclkdiv_shift; /* REFCLKDIV base */
+ uint32_t refclkdiv_width; /* REFCLKDIV width */
+};
+
+static int
+qcom_clk_fepll_recalc(struct clknode *clk, uint64_t *freq)
+{
+ struct qcom_clk_fepll_sc *sc;
+ uint64_t vco, parent_rate;
+ uint32_t reg, fdbkdiv, refclkdiv;
+
+ sc = clknode_get_softc(clk);
+
+ if (freq == NULL || *freq == 0) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: called; NULL or 0 frequency\n",
+ __func__);
+ return (ENXIO);
+ }
+
+ parent_rate = *freq;
+
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->offset, ®);
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ fdbkdiv = (reg >> sc->fdbkdiv_shift) &
+ ((1U << sc->fdbkdiv_width) - 1);
+ refclkdiv = (reg >> sc->refclkdiv_shift) &
+ ((1U << sc->refclkdiv_width) - 1);
+
+ vco = parent_rate / refclkdiv;
+ vco = vco * 2;
+ vco = vco * fdbkdiv;
+
+ *freq = vco;
+ return (0);
+}
+
+static int
+qcom_clk_fepll_init(struct clknode *clk, device_t dev)
+{
+
+ /*
+ * There's only a single parent here for an FEPLL, so just set it
+ * to 0; the caller doesn't need to supply it.
+ */
+ clknode_init_parent_idx(clk, 0);
+
+ return (0);
+}
+
+static clknode_method_t qcom_clk_fepll_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, qcom_clk_fepll_init),
+ CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_fepll_recalc),
+ CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_fepll_class, qcom_clk_fepll_methods,
+ sizeof(struct qcom_clk_fepll_sc), clknode_class);
+
+int
+qcom_clk_fepll_register(struct clkdom *clkdom,
+ struct qcom_clk_fepll_def *clkdef)
+{
+ struct clknode *clk;
+ struct qcom_clk_fepll_sc *sc;
+
+ clk = clknode_create(clkdom, &qcom_clk_fepll_class, &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clknode = clk;
+
+ sc->offset = clkdef->offset;
+ sc->fdbkdiv_shift = clkdef->fdbkdiv_shift;
+ sc->fdbkdiv_width = clkdef->fdbkdiv_width;
+ sc->refclkdiv_shift = clkdef->refclkdiv_shift;
+ sc->refclkdiv_width = clkdef->refclkdiv_width;
+
+ clknode_register(clkdom, clk);
+
+ return (0);
+}
diff --git a/sys/dev/qcom_clk/qcom_clk_freqtbl.h b/sys/dev/qcom_clk/qcom_clk_freqtbl.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_freqtbl.h
@@ -0,0 +1,44 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_FREQTBL_H__
+#define __QCOM_CLK_FREQTBL_H__
+
+struct qcom_clk_freq_tbl {
+ uint64_t freq;
+ const char *parent;
+ uint32_t pre_div;
+ uint32_t m;
+ uint32_t n;
+};
+
+const struct qcom_clk_freq_tbl * qcom_clk_freq_tbl_lookup(
+ const struct qcom_clk_freq_tbl *tbl, uint64_t freq);
+
+#endif /* __QCOM_CLK_FREQTBL_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_freqtbl.c b/sys/dev/qcom_clk/qcom_clk_freqtbl.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_freqtbl.c
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include "qcom_clk_freqtbl.h"
+
+/*
+ * Walk the list of frequencies and return the highest frequency supported.
+ */
+const struct qcom_clk_freq_tbl *
+qcom_clk_freq_tbl_lookup(const struct qcom_clk_freq_tbl *tbl, uint64_t freq)
+{
+ const struct qcom_clk_freq_tbl *t;
+
+ if (tbl == NULL)
+ return (NULL);
+
+ for (t = tbl; t->freq !=0; t++) {
+ if (freq <= t->freq)
+ return (t);
+ }
+
+ return (NULL);
+}
diff --git a/sys/dev/qcom_clk/qcom_clk_rcg2.h b/sys/dev/qcom_clk/qcom_clk_rcg2.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_rcg2.h
@@ -0,0 +1,61 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_RCG2_H__
+#define __QCOM_CLK_RCG2_H__
+
+#include "qcom_clk_freqtbl.h"
+
+/* Flags */
+/* Set the rate on the parent clock, not just ours */
+#define QCOM_CLK_RCG2_FLAGS_SET_RATE_PARENT 0x1
+/* Must not stop this clock/gate! */
+#define QCOM_CLK_RCG2_FLAGS_CRITICAL 0x2
+
+/* prediv to hw mapping */
+#define QCOM_CLK_FREQTBL_PREDIV_RCG2(prediv) (2*(prediv)-1)
+
+struct qcom_clk_rcg2_def {
+ struct clknode_init_def clkdef;
+ uint32_t cmd_rcgr; /* rcg2 register start */
+ uint32_t hid_width; /* pre-divisor width */
+ uint32_t mnd_width; /* mn:d divisor width */
+ int32_t safe_src_idx; /* safe parent when disabling a shared
+ * rcg2 */
+ uint32_t cfg_offset; /* cfg offset after cmd_rcgr */
+ int32_t safe_pre_parent_idx; /* safe parent before switching
+ * parent mux */
+ uint32_t flags;
+ const struct qcom_clk_freq_tbl *freq_tbl;
+};
+
+extern int qcom_clk_rcg2_register(struct clkdom *clkdom,
+ struct qcom_clk_rcg2_def *clkdef);
+
+#endif /* __QCOM_CLK_RCG2_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_rcg2.c b/sys/dev/qcom_clk/qcom_clk_rcg2.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_rcg2.c
@@ -0,0 +1,660 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/clk/clk_div.h>
+#include <dev/extres/clk/clk_fixed.h>
+#include <dev/extres/clk/clk_mux.h>
+
+#include "qcom_clk_freqtbl.h"
+#include "qcom_clk_rcg2.h"
+#include "qcom_clk_rcg2_reg.h"
+
+#include "clkdev_if.h"
+
+#if 0
+#define DPRINTF(dev, msg...) device_printf(dev, msg);
+#else
+#define DPRINTF(dev, msg...)
+#endif
+
+#define QCOM_CLK_RCG2_CFG_OFFSET(sc) \
+ ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_CFG_REG)
+#define QCOM_CLK_RCG2_CMD_REGISTER(sc) \
+ ((sc)->cmd_rcgr + QCOM_CLK_RCG2_CMD_REG)
+#define QCOM_CLK_RCG2_M_OFFSET(sc) \
+ ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_M_REG)
+#define QCOM_CLK_RCG2_N_OFFSET(sc) \
+ ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_N_REG)
+#define QCOM_CLK_RCG2_D_OFFSET(sc) \
+ ((sc)->cmd_rcgr + (sc)->cfg_offset + QCOM_CLK_RCG2_D_REG)
+
+struct qcom_clk_rcg2_sc {
+ struct clknode *clknode;
+ uint32_t cmd_rcgr;
+ uint32_t hid_width;
+ uint32_t mnd_width;
+ int32_t safe_src_idx;
+ uint32_t cfg_offset;
+ int safe_pre_parent_idx;
+ uint32_t flags;
+ const struct qcom_clk_freq_tbl *freq_tbl;
+};
+
+
+/*
+ * Finish a clock update.
+ *
+ * This instructs the configuration to take effect.
+ */
+static bool
+qcom_clk_rcg2_update_config_locked(struct qcom_clk_rcg2_sc *sc)
+{
+ uint32_t reg, count;
+
+ /*
+ * Send "update" to the controller.
+ */
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
+ reg |= QCOM_CLK_RCG2_CMD_UPDATE;
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CMD_REGISTER(sc), reg);
+ wmb();
+
+ /*
+ * Poll for completion of update.
+ */
+ for (count = 0; count < 1000; count++) {
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
+ if ((reg & QCOM_CLK_RCG2_CMD_UPDATE) == 0) {
+ return (true);
+ }
+ DELAY(10);
+ rmb();
+ }
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
+ DPRINTF(clknode_get_device(sc->clknode), "%s: failed; reg=0x%08x\n",
+ __func__, reg);
+ return (false);
+}
+
+/*
+ * Calculate the output frequency given an input frequency and the m/n:d
+ * configuration.
+ */
+static uint64_t
+qcom_clk_rcg2_calc_rate(uint64_t rate, uint32_t mode, uint32_t m, uint32_t n,
+ uint32_t hid_div)
+{
+ if (hid_div != 0) {
+ rate = rate * 2;
+ rate = rate / (hid_div + 1);
+ }
+
+ /* Note: assume n is not 0 here; bad things happen if it is */
+
+ if (mode != 0) {
+ rate = (rate * m) / n;
+ }
+
+ return (rate);
+}
+
+/*
+ * The inverse of calc_rate() - calculate the required input frequency
+ * given the desired output freqency and m/n:d configuration.
+ */
+static uint64_t
+qcom_clk_rcg2_calc_input_freq(uint64_t freq, uint32_t m, uint32_t n,
+ uint32_t hid_div)
+{
+ if (hid_div != 0) {
+ freq = freq / 2;
+ freq = freq * (hid_div + 1);
+ }
+
+ if (n != 0) {
+ freq = (freq * n) / m;
+ }
+
+ return (freq);
+}
+
+static int
+qcom_clk_rcg2_recalc(struct clknode *clk, uint64_t *freq)
+{
+ struct qcom_clk_rcg2_sc *sc;
+ uint32_t cfg, m = 0, n = 0, hid_div = 0;
+ uint32_t mode = 0, mask;
+
+ sc = clknode_get_softc(clk);
+
+ /* Read the MODE, CFG, M and N parameters */
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CFG_OFFSET(sc),
+ &cfg);
+ if (sc->mnd_width != 0) {
+ mask = (1U << sc->mnd_width) - 1;
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_M_OFFSET(sc), &m);
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_N_OFFSET(sc), &n);
+ m = m & mask;
+ n = ~ n;
+ n = n & mask;
+ n = n + m;
+ mode = (cfg & QCOM_CLK_RCG2_CFG_MODE_MASK)
+ >> QCOM_CLK_RCG2_CFG_MODE_SHIFT;
+ }
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ /* Fetch the divisor */
+ mask = (1U << sc->hid_width) - 1;
+ hid_div = (cfg >> QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT) & mask;
+
+ /* Calculate the rate based on the parent rate and config */
+ *freq = qcom_clk_rcg2_calc_rate(*freq, mode, m, n, hid_div);
+
+ return (0);
+}
+
+/*
+ * configure the mn:d divisor, pre-divisor, and parent.
+ */
+static void
+qcom_clk_rcg2_set_config_locked(struct qcom_clk_rcg2_sc *sc,
+ const struct qcom_clk_freq_tbl *f, int parent_idx)
+{
+ uint32_t mask, reg;
+
+ /* If we have MN:D, then update it */
+ if (sc->mnd_width != 0 && f->n != 0) {
+ mask = (1U << sc->mnd_width) - 1;
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_M_OFFSET(sc), ®);
+ reg &= ~mask;
+ reg |= (f->m & mask);
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_M_OFFSET(sc), reg);
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_N_OFFSET(sc), ®);
+ reg &= ~mask;
+ reg |= ((~(f->n - f->m)) & mask);
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_N_OFFSET(sc), reg);
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_D_OFFSET(sc), ®);
+ reg &= ~mask;
+ reg |= ((~f->n) & mask);
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_D_OFFSET(sc), reg);
+ }
+
+ mask = (1U << sc->hid_width) - 1;
+ /*
+ * Mask out register fields we're going to modify along with
+ * the pre-divisor.
+ */
+ mask |= QCOM_CLK_RCG2_CFG_SRC_SEL_MASK
+ | QCOM_CLK_RCG2_CFG_MODE_MASK
+ | QCOM_CLK_RCG2_CFG_HW_CLK_CTRL_MASK;
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
+ reg &= ~mask;
+
+ /* Configure pre-divisor */
+ reg = reg | ((f->pre_div) << QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT);
+
+ /* Configure parent clock */
+ reg = reg | (((parent_idx << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
+ & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK));
+
+ /* Configure dual-edge if needed */
+ if (sc->mnd_width != 0 && f->n != 0 && (f->m != f->n))
+ reg |= QCOM_CLK_RCG2_CFG_MODE_DUAL_EDGE;
+
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CFG_OFFSET(sc), reg);
+}
+
+static int
+qcom_clk_rcg2_init(struct clknode *clk, device_t dev)
+{
+ struct qcom_clk_rcg2_sc *sc;
+ uint32_t reg;
+ uint32_t idx;
+ bool enabled;
+
+ sc = clknode_get_softc(clk);
+
+ /*
+ * Read the mux setting to set the right parent.
+ * Whilst here, read the config to get whether we're enabled
+ * or not.
+ */
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ /* check if rcg2 root clock is enabled */
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CMD_REGISTER(sc), ®);
+ if (reg & QCOM_CLK_RCG2_CMD_ROOT_OFF)
+ enabled = false;
+ else
+ enabled = true;
+ /* mux settings */
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ idx = (reg & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK)
+ >> QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT;
+ DPRINTF(clknode_get_device(sc->clknode), "%s: mux index %u\n",
+ __func__, idx);
+ clknode_init_parent_idx(clk, idx);
+
+ /*
+ * If we could be sure our parent clocks existed here in the tree,
+ * we could calculate our current frequency by fetching the parent
+ * frequency and then do our divider math. Unfortunately that
+ * currently isn't the case.
+ */
+
+ return(0);
+}
+
+static int
+qcom_clk_rcg2_set_gate(struct clknode *clk, bool enable)
+{
+
+ /*
+ * For now this isn't supported; there's some support for
+ * "shared" rcg2 nodes in the Qualcomm/upstream Linux trees but
+ * it's not currently needed for the supported platforms.
+ */
+ return (0);
+}
+
+/*
+ * Program the parent index.
+ *
+ * This doesn't do the update. It also must be called with the device
+ * lock held.
+ */
+static void
+qcom_clk_rcg2_set_parent_index_locked(struct qcom_clk_rcg2_sc *sc,
+ uint32_t index)
+{
+ uint32_t reg;
+
+ CLKDEV_READ_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CFG_OFFSET(sc), ®);
+ reg = reg & ~QCOM_CLK_RCG2_CFG_SRC_SEL_MASK;
+ reg = reg | (((index << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
+ & QCOM_CLK_RCG2_CFG_SRC_SEL_MASK));
+ CLKDEV_WRITE_4(clknode_get_device(sc->clknode),
+ QCOM_CLK_RCG2_CFG_OFFSET(sc),
+ reg);
+}
+
+/*
+ * Set frequency
+ *
+ * fin - the parent frequency, if exists
+ * fout - starts as the requested frequency, ends with the configured
+ * or dry-run frequency
+ * Flags - CLK_SET_DRYRUN, CLK_SET_ROUND_UP, CLK_SET_ROUND_DOWN
+ * retval - 0, ERANGE
+ */
+static int
+qcom_clk_rcg2_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
+ int flags, int *stop)
+{
+ struct qcom_clk_rcg2_sc *sc;
+ const struct qcom_clk_freq_tbl *f;
+ const char **parent_names;
+ uint64_t p_freq, p_clk_freq;
+ int parent_cnt;
+ struct clknode *p_clk;
+ int i;
+
+ sc = clknode_get_softc(clk);
+
+ /*
+ * Find a suitable frequency in the frequency table.
+ *
+ * TODO: should pay attention to ROUND_UP / ROUND_DOWN and add
+ * a freqtbl method to handle both accordingly.
+ */
+ f = qcom_clk_freq_tbl_lookup(sc->freq_tbl, *fout);
+ if (f == NULL) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: no suitable freqtbl entry found for freq %llu\n",
+ __func__,
+ *fout);
+ return (ERANGE);
+ }
+
+ /*
+ * Find the parent index for the given parent clock.
+ * Abort if we can't actually find it.
+ *
+ * XXX TODO: this should be a clk API call!
+ */
+ parent_cnt = clknode_get_parents_num(clk);
+ parent_names = clknode_get_parent_names(clk);
+ for (i = 0; i < parent_cnt; i++) {
+ if (parent_names[i] == NULL)
+ continue;
+ if (strcmp(parent_names[i], f->parent) == 0)
+ break;
+ }
+ if (i >= parent_cnt) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't find suitable parent?\n",
+ __func__);
+ return (ENXIO);
+ }
+
+ /*
+ * If we aren't setting the parent clock, then we need
+ * to just program the new parent clock in and update.
+ * (or for DRYRUN just skip that and return the new
+ * frequency.)
+ */
+ if ((sc->flags & QCOM_CLK_RCG2_FLAGS_SET_RATE_PARENT) == 0) {
+ if (flags & CLK_SET_DRYRUN) {
+ *fout = f->freq;
+ return (0);
+ }
+
+ if (sc->safe_pre_parent_idx > -1) {
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: setting to safe parent idx %d\n",
+ __func__,
+ sc->safe_pre_parent_idx);
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ qcom_clk_rcg2_set_parent_index_locked(sc,
+ sc->safe_pre_parent_idx);
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: safe parent: updating config\n", __func__);
+ if (! qcom_clk_rcg2_update_config_locked(sc)) {
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: error updating config\n",
+ __func__);
+ return (ENXIO);
+ }
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: safe parent: done\n", __func__);
+ clknode_set_parent_by_idx(sc->clknode,
+ sc->safe_pre_parent_idx);
+ }
+ /* Program parent index, then schedule update */
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ qcom_clk_rcg2_set_parent_index_locked(sc, i);
+ if (! qcom_clk_rcg2_update_config_locked(sc)) {
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't program in parent idx %u!\n",
+ __func__, i);
+ return (ENXIO);
+ }
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ clknode_set_parent_by_idx(sc->clknode, i);
+ *fout = f->freq;
+ return (0);
+ }
+
+ /*
+ * If we /are/ setting the parent clock, then we need
+ * to determine what frequency we need the parent to
+ * be, and then reconfigure the parent to the new
+ * frequency, and then change our parent.
+ *
+ * (Again, if we're doing DRYRUN, just skip that
+ * and return the new frequency.)
+ */
+ p_clk = clknode_find_by_name(f->parent);
+ if (p_clk == NULL) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't find parent clk (%s)\n",
+ __func__, f->parent);
+ return (ENXIO);
+ }
+
+ /*
+ * Calculate required frequency from said parent clock to
+ * meet the needs of our target clock.
+ */
+ p_freq = qcom_clk_rcg2_calc_input_freq(f->freq, f->m, f->n,
+ f->pre_div);
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: request %llu, parent %s freq %llu, parent freq %llu\n",
+ __func__,
+ *fout,
+ f->parent,
+ f->freq,
+ p_freq);
+
+ /*
+ * To ensure glitch-free operation on some clocks, set it to
+ * a safe parent before programming our divisor and the parent
+ * clock configuration. Then once it's done, flip the parent
+ * to the new parent.
+ *
+ * If we're doing a dry-run then we don't need to re-parent the
+ * clock just yet!
+ */
+ if (((flags & CLK_SET_DRYRUN) == 0) &&
+ (sc->safe_pre_parent_idx > -1)) {
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: setting to safe parent idx %d\n",
+ __func__,
+ sc->safe_pre_parent_idx);
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ qcom_clk_rcg2_set_parent_index_locked(sc,
+ sc->safe_pre_parent_idx);
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: safe parent: updating config\n", __func__);
+ if (! qcom_clk_rcg2_update_config_locked(sc)) {
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: error updating config\n",
+ __func__);
+ return (ENXIO);
+ }
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: safe parent: done\n", __func__);
+ clknode_set_parent_by_idx(sc->clknode,
+ sc->safe_pre_parent_idx);
+ }
+
+ /*
+ * Set the parent frequency before we change our mux and divisor
+ * configuration.
+ */
+ if (clknode_get_freq(p_clk, &p_clk_freq) != 0) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't get freq for parent clock %s\n",
+ __func__,
+ f->parent);
+ return (ENXIO);
+ }
+ if (p_clk_freq != p_freq) {
+ uint64_t n_freq;
+ int rv;
+
+ /*
+ * If we're doing a dryrun then call test_freq() not set_freq().
+ * That way we get the frequency back that we would be set to.
+ *
+ * If we're not doing a dry run then set the frequency, then
+ * call get_freq to get what it was set to.
+ */
+ if (flags & CLK_SET_DRYRUN) {
+ n_freq = p_freq;
+ rv = clknode_test_freq(p_clk, n_freq, flags, 0,
+ &p_freq);
+ } else {
+ rv = clknode_set_freq(p_clk, p_freq, flags, 0);
+ }
+
+ if (rv != 0) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't set parent clock %s frequency to "
+ "%llu\n",
+ __func__,
+ f->parent,
+ p_freq);
+ return (ENXIO);
+ }
+
+ /* Frequency was set, fetch what it was set to */
+ if ((flags & CLK_SET_DRYRUN) == 0) {
+ rv = clknode_get_freq(p_clk, &p_freq);
+ if (rv != 0) {
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't get parent frequency",
+ __func__);
+ return (ENXIO);
+ }
+ }
+ }
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: requsted freq=%llu, target freq=%llu,"
+ " parent choice=%s, parent_freq=%llu\n",
+ __func__,
+ *fout,
+ f->freq,
+ f->parent,
+ p_freq);
+
+ /*
+ * Set the parent node, the parent programming and the divisor
+ * config. Because they're done together, we don't go via
+ * a mux method on this node.
+ */
+
+ /*
+ * Program the divisor and parent.
+ */
+ if ((flags & CLK_SET_DRYRUN) == 0) {
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ qcom_clk_rcg2_set_config_locked(sc, f, i);
+ if (! qcom_clk_rcg2_update_config_locked(sc)) {
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ device_printf(clknode_get_device(sc->clknode),
+ "%s: couldn't program in divisor, help!\n",
+ __func__);
+ return (ENXIO);
+ }
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+ clknode_set_parent_by_idx(sc->clknode, i);
+ }
+
+ /*
+ * p_freq is now the frequency that the parent /is/ set to.
+ * (Or would be set to for a dry run.)
+ *
+ * Calculate what the eventual frequency would be, we'll want
+ * this to return when we're done - and again, if it's a dryrun,
+ * don't set anything up. This doesn't rely on the register
+ * contents.
+ */
+ *fout = qcom_clk_rcg2_calc_rate(p_freq, (f->n == 0 ? 0 : 1),
+ f->m, f->n, f->pre_div);
+
+ return (0);
+}
+
+static clknode_method_t qcom_clk_rcg2_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, qcom_clk_rcg2_init),
+ CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_rcg2_recalc),
+ CLKNODEMETHOD(clknode_set_gate, qcom_clk_rcg2_set_gate),
+ CLKNODEMETHOD(clknode_set_freq, qcom_clk_rcg2_set_freq),
+ CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_rcg2_class, qcom_clk_rcg2_methods,
+ sizeof(struct qcom_clk_rcg2_sc), clknode_class);
+
+int
+qcom_clk_rcg2_register(struct clkdom *clkdom,
+ struct qcom_clk_rcg2_def *clkdef)
+{
+ struct clknode *clk;
+ struct qcom_clk_rcg2_sc *sc;
+
+ /*
+ * Right now the rcg2 code isn't supporting turning off the clock
+ * or limiting it to the lowest parent clock. But, do set the
+ * flags appropriately.
+ */
+ if (clkdef->flags & QCOM_CLK_RCG2_FLAGS_CRITICAL)
+ clkdef->clkdef.flags |= CLK_NODE_CANNOT_STOP;
+
+ clk = clknode_create(clkdom, &qcom_clk_rcg2_class, &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clknode = clk;
+
+ sc->cmd_rcgr = clkdef->cmd_rcgr;
+ sc->hid_width = clkdef->hid_width;
+ sc->mnd_width = clkdef->mnd_width;
+ sc->safe_src_idx = clkdef->safe_src_idx;
+ sc->safe_pre_parent_idx = clkdef->safe_pre_parent_idx;
+ sc->cfg_offset = clkdef->cfg_offset;
+ sc->flags = clkdef->flags;
+ sc->freq_tbl = clkdef->freq_tbl;
+
+ clknode_register(clkdom, clk);
+
+ return (0);
+}
diff --git a/sys/dev/qcom_clk/qcom_clk_rcg2_reg.h b/sys/dev/qcom_clk/qcom_clk_rcg2_reg.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_rcg2_reg.h
@@ -0,0 +1,58 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_RCG2_REG_H__
+#define __QCOM_CLK_RCG2_REG_H__
+
+#define QCOM_CLK_RCG2_CMD_REG 0x0
+#define QCOM_CLK_RCG2_CMD_UPDATE (1U << 0)
+#define QCOM_CLK_RCG2_CMD_ROOT_EN (1U << 1)
+#define QCOM_CLK_RCG2_CMD_DIRTY_CFG (1U << 4)
+#define QCOM_CLK_RCG2_CMD_DIRTY_N (1U << 5)
+#define QCOM_CLK_RCG2_CMD_DIRTY_M (1U << 6)
+#define QCOM_CLK_RCG2_CMD_DIRTY_D (1U << 7)
+#define QCOM_CLK_RCG2_CMD_ROOT_OFF (1U << 31)
+
+#define QCOM_CLK_RCG2_CFG_REG 0x4
+#define QCOM_CLK_RCG2_CFG_SRC_DIV_SHIFT 0
+#define QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT 8
+#define QCOM_CLK_RCG2_CFG_SRC_SEL_MASK \
+ (0x7 << QCOM_CLK_RCG2_CFG_SRC_SEL_SHIFT)
+#define QCOM_CLK_RCG2_CFG_MODE_SHIFT 12
+#define QCOM_CLK_RCG2_CFG_MODE_MASK \
+ (0x3 << QCOM_CLK_RCG2_CFG_MODE_SHIFT)
+#define QCOM_CLK_RCG2_CFG_MODE_DUAL_EDGE \
+ (0x2 << QCOM_CLK_RCG2_CFG_MODE_SHIFT)
+#define QCOM_CLK_RCG2_CFG_HW_CLK_CTRL_MASK (1U << 20)
+
+#define QCOM_CLK_RCG2_M_REG 0x8
+#define QCOM_CLK_RCG2_N_REG 0xc
+#define QCOM_CLK_RCG2_D_REG 0x10
+
+#endif /* __QCOM_CLK_RCG2_REG_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_ro_div.h b/sys/dev/qcom_clk/qcom_clk_ro_div.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_ro_div.h
@@ -0,0 +1,49 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef __QCOM_CLK_RO_DIV_H__
+#define __QCOM_CLK_RO_DIV_H__
+
+struct qcom_clk_ro_div_tbl {
+ uint32_t val;
+ uint32_t div;
+};
+
+struct qcom_clk_ro_div_def {
+ struct clknode_init_def clkdef;
+ uint32_t offset; /* register offset */
+ uint32_t shift; /* field shift */
+ uint32_t width; /* field width */
+ struct qcom_clk_ro_div_tbl *div_tbl;
+};
+
+extern int qcom_clk_ro_div_register(struct clkdom *clkdom,
+ struct qcom_clk_ro_div_def *clkdef);
+
+#endif /* __QCOM_CLK_RO_DIV_H__ */
diff --git a/sys/dev/qcom_clk/qcom_clk_ro_div.c b/sys/dev/qcom_clk/qcom_clk_ro_div.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/qcom_clk/qcom_clk_ro_div.c
@@ -0,0 +1,153 @@
+/*-
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+#include <machine/bus.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/extres/clk/clk_div.h>
+#include <dev/extres/clk/clk_fixed.h>
+#include <dev/extres/clk/clk_mux.h>
+
+#include "qcom_clk_ro_div.h"
+
+#include "clkdev_if.h"
+
+#if 0
+#define DPRINTF(dev, msg...) device_printf(dev, "cpufreq_dt: " msg);
+#else
+#define DPRINTF(dev, msg...)
+#endif
+
+/*
+ * This is a read-only divisor table node.
+ * It represents some divisor that is setup by the boot environment
+ * and we don't have any need for the driver to go and fiddle with.
+ *
+ * It likely should just live in the extres/clk code.
+ */
+
+struct qcom_clk_ro_div_sc {
+ struct clknode *clknode;
+ uint32_t offset;
+ uint32_t shift;
+ uint32_t width;
+ struct qcom_clk_ro_div_tbl *div_tbl;
+};
+
+static int
+qcom_clk_ro_div_recalc(struct clknode *clk, uint64_t *freq)
+{
+ struct qcom_clk_ro_div_sc *sc;
+ uint32_t reg, idx, div = 1;
+ int i;
+
+ sc = clknode_get_softc(clk);
+
+ if (freq == NULL || *freq == 0) {
+ printf("%s: called; NULL or 0 frequency\n", __func__);
+ return (ENXIO);
+ }
+
+ CLKDEV_DEVICE_LOCK(clknode_get_device(sc->clknode));
+ CLKDEV_READ_4(clknode_get_device(sc->clknode), sc->offset, ®);
+ CLKDEV_DEVICE_UNLOCK(clknode_get_device(sc->clknode));
+
+ idx = (reg >> sc->shift) & ((1U << sc->width) - 1);
+
+ for (i = 0; (sc->div_tbl[i].div != 0); i++) {
+ if (idx == sc->div_tbl[i].val) {
+ div = sc->div_tbl[i].div;
+ break;
+ }
+ }
+
+ DPRINTF(clknode_get_device(sc->clknode),
+ "%s: freq=%llu, idx=%u, div=%u, out_freq=%llu\n",
+ __func__,
+ *freq,
+ idx,
+ div,
+ *freq / div);
+
+ *freq = *freq / div;
+ return (0);
+}
+
+static int
+qcom_clk_ro_div_init(struct clknode *clk, device_t dev)
+{
+
+ /*
+ * There's only a single parent here for this divisor,
+ * so just set it to 0; the caller doesn't need to supply it.
+ */
+ clknode_init_parent_idx(clk, 0);
+
+ return (0);
+}
+
+static clknode_method_t qcom_clk_ro_div_methods[] = {
+ /* Device interface */
+ CLKNODEMETHOD(clknode_init, qcom_clk_ro_div_init),
+ CLKNODEMETHOD(clknode_recalc_freq, qcom_clk_ro_div_recalc),
+ CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(qcom_clk_fepll, qcom_clk_ro_div_class,
+ qcom_clk_ro_div_methods, sizeof(struct qcom_clk_ro_div_sc),
+ clknode_class);
+
+int
+qcom_clk_ro_div_register(struct clkdom *clkdom,
+ struct qcom_clk_ro_div_def *clkdef)
+{
+ struct clknode *clk;
+ struct qcom_clk_ro_div_sc *sc;
+
+ clk = clknode_create(clkdom, &qcom_clk_ro_div_class,
+ &clkdef->clkdef);
+ if (clk == NULL)
+ return (1);
+
+ sc = clknode_get_softc(clk);
+ sc->clknode = clk;
+ sc->offset = clkdef->offset;
+ sc->shift = clkdef->shift;
+ sc->width = clkdef->width;
+ sc->div_tbl = clkdef->div_tbl;
+
+ clknode_register(clkdom, clk);
+
+ return (0);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Nov 27, 12:12 AM (3 h, 37 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
26236276
Default Alt Text
D33665.id54425.diff (70 KB)
Attached To
Mode
D33665: Add support for qualcomm clock nodes the the IPQ4018/IPQ4019 clock tree.
Attached
Detach File
Event Timeline
Log In to Comment