Index: sys/arm/qualcomm/qcom_gcc_ipq4018_clock.c =================================================================== --- sys/arm/qualcomm/qcom_gcc_ipq4018_clock.c +++ /dev/null @@ -1,82 +0,0 @@ -/*- - * SPDX-License-Identifier: BSD-2-Clause-FreeBSD - * - * Copyright (c) 2021, Adrian Chadd - * - * 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 unmodified, 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 ``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 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. - */ - -/* Driver for Qualcomm IPQ4018 clock and reset device */ - -#include -__FBSDID("$FreeBSD$"); - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - -#include - - -/* - * Fixed frequency clock sources: - * - * P_XO - 48MHz - * sleep-clk - is really 32KHz, although older DTS have it as 32.768KHz - */ - -/* - * PLL derived sources: - * - * P_FEPLL125 - 125MHz - * P_FEPLL125DLY - 125MHz - * P_FEPLL200 - 200MHz - * P_FEPLL500 - 500MHz - * P_FEPLLWCSS2G - TBD - * P_FEPLLWCSS5G - TBD - * - * Then there are two DDR PLLs which are treated/configured slightly - * differently: - * - * P_DDRPLLAPSS - TBD - * P_DDRPLLSDCC - TBD - */ - -/* - * Interesting stuff in Linux whilst I reverse engineer + figure it out: - * /sys/kernel/debug/clk - */ Index: sys/arm/qualcomm/std.ipq4018 =================================================================== --- sys/arm/qualcomm/std.ipq4018 +++ sys/arm/qualcomm/std.ipq4018 @@ -4,9 +4,18 @@ arm/qualcomm/qcom_cpu_kpssv2.c optional smp dev/qcom_rnd/qcom_rnd.c optional qcom_rnd -arm/qualcomm/qcom_gcc_ipq4018.c optional qcom_gcc_ipq4018 -arm/qualcomm/qcom_gcc_ipq4018_reset.c optional qcom_gcc_ipq4018 -arm/qualcomm/qcom_gcc_ipq4018_clock.c optional qcom_gcc_ipq4018 + +dev/qcom_gcc/qcom_gcc_ipq4018.c optional qcom_gcc_ipq4018 +dev/qcom_gcc/qcom_gcc_ipq4018_reset.c optional qcom_gcc_ipq4018 +dev/qcom_gcc/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 Index: sys/dev/qcom_clk/qcom_clk_apssdiv.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_apssdiv.h +++ sys/dev/qcom_clk/qcom_clk_apssdiv.h @@ -27,24 +27,22 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_APSS_H__ +#define __QCOM_CLK_APSS_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; -}; +#include "qcom_clk_freqtbl.h" -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; +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_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +extern int qcom_clk_apssdiv_register(struct clkdom *clkdom, + struct qcom_clk_apssdiv_def *clkdef); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_APSS_H__ */ Index: sys/dev/qcom_clk/qcom_clk_apssdiv.c =================================================================== --- /dev/null +++ sys/dev/qcom_clk/qcom_clk_apssdiv.c @@ -0,0 +1,287 @@ +/*- + * Copyright (c) 2021 Adrian Chadd . + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} Index: sys/dev/qcom_clk/qcom_clk_branch2.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_branch2.h +++ sys/dev/qcom_clk/qcom_clk_branch2.h @@ -27,24 +27,44 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_BRANCH2_H__ +#define __QCOM_CLK_BRANCH2_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; -}; +#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 */ -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; + uint32_t halt_check_type; + bool halt_check_voted; /* whether to delay when waiting */ }; -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +extern int qcom_clk_branch2_register(struct clkdom *clkdom, + struct qcom_clk_branch2_def *clkdef); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_BRANCH2_H__ */ Index: sys/dev/qcom_clk/qcom_clk_branch2.c =================================================================== --- /dev/null +++ sys/dev/qcom_clk/qcom_clk_branch2.c @@ -0,0 +1,290 @@ +/*- + * Copyright (c) 2021 Adrian Chadd . + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} Index: sys/dev/qcom_clk/qcom_clk_branch2_reg.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_branch2_reg.h +++ sys/dev/qcom_clk/qcom_clk_branch2_reg.h @@ -27,24 +27,13 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_BRANCH2_REG_H__ +#define __QCOM_CLK_BRANCH2_REG_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; -}; +#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) -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; -}; - -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); - -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_BRANCH2_REG_H__ */ Index: sys/dev/qcom_clk/qcom_clk_fdiv.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_fdiv.h +++ sys/dev/qcom_clk/qcom_clk_fdiv.h @@ -27,24 +27,15 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_FDIV_H__ +#define __QCOM_CLK_FDIV_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; +struct qcom_clk_fdiv_def { + struct clknode_init_def clkdef; + uint32_t divisor; /* Fixed divisor */ }; -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; -}; - -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +extern int qcom_clk_fdiv_register(struct clkdom *clkdom, + struct qcom_clk_fdiv_def *clkdef); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_FDIV_H__ */ Index: sys/dev/qcom_clk/qcom_clk_fdiv.c =================================================================== --- /dev/null +++ sys/dev/qcom_clk/qcom_clk_fdiv.c @@ -0,0 +1,115 @@ +/*- + * Copyright (c) 2021 Adrian Chadd . + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} Index: sys/dev/qcom_clk/qcom_clk_fepll.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_fepll.h +++ sys/dev/qcom_clk/qcom_clk_fepll.h @@ -27,24 +27,19 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_FEPLL_H__ +#define __QCOM_CLK_FEPLL_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; +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 */ }; -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; -}; - -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +extern int qcom_clk_fepll_register(struct clkdom *clkdom, + struct qcom_clk_fepll_def *clkdef); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_FEPLL_H__ */ Index: sys/dev/qcom_clk/qcom_clk_fepll.c =================================================================== --- /dev/null +++ sys/dev/qcom_clk/qcom_clk_fepll.c @@ -0,0 +1,153 @@ +/*- + * Copyright (c) 2021 Adrian Chadd . + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} Index: sys/dev/qcom_clk/qcom_clk_freqtbl.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_freqtbl.h +++ sys/dev/qcom_clk/qcom_clk_freqtbl.h @@ -27,24 +27,18 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_FREQTBL_H__ +#define __QCOM_CLK_FREQTBL_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; +struct qcom_clk_freq_tbl { + uint64_t freq; + const char *parent; + uint32_t pre_div; + uint32_t m; + uint32_t n; }; -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; -}; - -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +const struct qcom_clk_freq_tbl * qcom_clk_freq_tbl_lookup( + const struct qcom_clk_freq_tbl *tbl, uint64_t freq); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_FREQTBL_H__ */ Index: sys/dev/qcom_clk/qcom_clk_freqtbl.c =================================================================== --- sys/dev/qcom_clk/qcom_clk_freqtbl.c +++ sys/dev/qcom_clk/qcom_clk_freqtbl.c @@ -1,7 +1,5 @@ /*- - * SPDX-License-Identifier: BSD-2-Clause-FreeBSD - * - * Copyright (c) 2021 Adrian Chadd + * Copyright (c) 2021 Adrian Chadd . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -23,28 +21,36 @@ * 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_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; -}; +#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; -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; -}; + if (tbl == NULL) + return (NULL); -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); + for (t = tbl; t->freq !=0; t++) { + if (freq <= t->freq) + return (t); + } -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ + return (NULL); +} Index: sys/dev/qcom_clk/qcom_clk_rcg2.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_rcg2.h +++ sys/dev/qcom_clk/qcom_clk_rcg2.h @@ -27,24 +27,35 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_RCG2_H__ +#define __QCOM_CLK_RCG2_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; -}; +#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_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; +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_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +extern int qcom_clk_rcg2_register(struct clkdom *clkdom, + struct qcom_clk_rcg2_def *clkdef); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_RCG2_H__ */ Index: sys/dev/qcom_clk/qcom_clk_rcg2.c =================================================================== --- /dev/null +++ sys/dev/qcom_clk/qcom_clk_rcg2.c @@ -0,0 +1,660 @@ +/*- + * Copyright (c) 2021 Adrian Chadd . + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} Index: sys/dev/qcom_clk/qcom_clk_rcg2_reg.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_rcg2_reg.h +++ sys/dev/qcom_clk/qcom_clk_rcg2_reg.h @@ -27,24 +27,32 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_RCG2_REG_H__ +#define __QCOM_CLK_RCG2_REG_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; -}; +#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) -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; -}; +#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) -extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +#define QCOM_CLK_RCG2_M_REG 0x8 +#define QCOM_CLK_RCG2_N_REG 0xc +#define QCOM_CLK_RCG2_D_REG 0x10 -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_RCG2_REG_H__ */ Index: sys/dev/qcom_clk/qcom_clk_ro_div.h =================================================================== --- sys/dev/qcom_clk/qcom_clk_ro_div.h +++ sys/dev/qcom_clk/qcom_clk_ro_div.h @@ -27,24 +27,23 @@ * $FreeBSD$ */ -#ifndef __QCOM_GCC_IPQ4018_VAR_H__ -#define __QCOM_GCC_IPQ4018_VAR_H__ +#ifndef __QCOM_CLK_RO_DIV_H__ +#define __QCOM_CLK_RO_DIV_H__ -struct qcom_gcc_ipq4018_reset_entry { - uint32_t reg; - uint32_t bit; +struct qcom_clk_ro_div_tbl { + uint32_t val; + uint32_t div; }; -struct qcom_gcc_ipq4018_softc { - device_t dev; - int reg_rid; - struct resource *reg; - struct mtx mtx; +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_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, - bool reset); -extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, - bool *reset); +extern int qcom_clk_ro_div_register(struct clkdom *clkdom, + struct qcom_clk_ro_div_def *clkdef); -#endif /* __QCOM_GCC_IPQ4018_VAR_H__ */ +#endif /* __QCOM_CLK_RO_DIV_H__ */ Index: sys/dev/qcom_clk/qcom_clk_ro_div.c =================================================================== --- /dev/null +++ sys/dev/qcom_clk/qcom_clk_ro_div.c @@ -0,0 +1,153 @@ +/*- + * Copyright (c) 2021 Adrian Chadd . + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} Index: sys/dev/qcom_gcc/qcom_gcc_ipq4018.c =================================================================== --- sys/dev/qcom_gcc/qcom_gcc_ipq4018.c +++ sys/dev/qcom_gcc/qcom_gcc_ipq4018.c @@ -49,11 +49,12 @@ #include +#include "clkdev_if.h" #include "hwreset_if.h" #include -#include +#include "qcom_gcc_ipq4018_var.h" static int qcom_gcc_ipq4018_modevent(module_t, int, void *); @@ -121,6 +122,11 @@ */ hwreset_register_ofw_provider(dev); + /* + * Setup and register as a clock provider. + */ + qcom_gcc_ipq4018_clock_setup(sc); + return (0); } @@ -131,6 +137,10 @@ sc = device_get_softc(dev); + /* + * TBD - deregistering reset/clock resources. + */ + if (sc->reg != NULL) { bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->reg_rid, sc->reg); @@ -148,6 +158,13 @@ DEVMETHOD(hwreset_assert, qcom_gcc_ipq4018_hwreset_assert), DEVMETHOD(hwreset_is_asserted, qcom_gcc_ipq4018_hwreset_is_asserted), + /* Clock interface */ + DEVMETHOD(clkdev_read_4, qcom_gcc_ipq4018_clock_read), + DEVMETHOD(clkdev_write_4, qcom_gcc_ipq4018_clock_write), + DEVMETHOD(clkdev_modify_4, qcom_gcc_ipq4018_clock_modify), + DEVMETHOD(clkdev_device_lock, qcom_gcc_ipq4018_clock_lock), + DEVMETHOD(clkdev_device_unlock, qcom_gcc_ipq4018_clock_unlock), + DEVMETHOD_END }; @@ -160,8 +177,8 @@ EARLY_DRIVER_MODULE(qcom_gcc_ipq4018, simplebus, qcom_gcc_ipq4018_driver, qcom_gcc_ipq4018_devclass, qcom_gcc_ipq4018_modevent, 0, - BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); + BUS_PASS_CPU + BUS_PASS_ORDER_EARLY); EARLY_DRIVER_MODULE(qcom_gcc_ipq4018, ofwbus, qcom_gcc_ipq4018_driver, qcom_gcc_ipq4018_devclass, qcom_gcc_ipq4018_modevent, 0, - BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE); -MODULE_VERSION(qcom_gcc_ipq4018_random, 1); + BUS_PASS_CPU + BUS_PASS_ORDER_EARLY); +MODULE_VERSION(qcom_gcc_ipq4018, 1); Index: sys/dev/qcom_gcc/qcom_gcc_ipq4018_clock.c =================================================================== --- /dev/null +++ sys/dev/qcom_gcc/qcom_gcc_ipq4018_clock.c @@ -0,0 +1,770 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021, Adrian Chadd + * + * 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 unmodified, 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 ``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 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. + */ + +/* Driver for Qualcomm IPQ4018 clock and reset device */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "qcom_gcc_ipq4018_var.h" + + +/* Fixed rate clock. */ +#define F_RATE(_id, cname, _freq) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = cname, \ + .clkdef.parent_names = NULL, \ + .clkdef.parent_cnt = 0, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .freq = _freq, \ +} + +/* Linked clock. */ +#define F_LINK(_id, _cname) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = NULL, \ + .clkdef.parent_cnt = 0, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ +} + + +/* FEPLL clock */ +#define F_FEPLL(_id, _cname, _parent, _reg, _fs, _fw, _rs, _rw) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = (const char *[]){_parent}, \ + .clkdef.parent_cnt = 1, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .offset = _reg, \ + .fdbkdiv_shift = _fs, \ + .fdbkdiv_width = _fw, \ + .refclkdiv_shift = _rs, \ + .refclkdiv_width = _rw, \ +} + +/* Fixed divisor clock */ +#define F_FDIV(_id, _cname, _parent, _divisor) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = (const char *[]){_parent}, \ + .clkdef.parent_cnt = 1, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .divisor = _divisor, \ +} + +/* APSS DIV clock */ +#define F_APSSDIV(_id, _cname, _parent, _doffset, _dshift, _dwidth, \ + _eoffset, _eshift, _freqtbl) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = (const char *[]){_parent}, \ + .clkdef.parent_cnt = 1, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .div_offset = _doffset, \ + .div_width = _dwidth, \ + .div_shift = _dshift, \ + .enable_offset = _eoffset, \ + .enable_shift = _eshift, \ + .freq_tbl = _freqtbl, \ +} + +/* read-only div table */ +#define F_RO_DIV(_id, _cname, _parent, _offset, _shift, _width, _tbl) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = (const char *[]){_parent}, \ + .clkdef.parent_cnt = 1, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .offset = _offset, \ + .width = _width, \ + .shift = _shift, \ + .div_tbl = _tbl, \ +} + +/* RCG2 clock */ +#define F_RCG2(_id, _cname, _parents, _rcgr, _hid_width, _mnd_width, \ + _safe_src_idx, _safe_pre_parent_idx, _cfg_offset, _flags, \ + _freq_tbl) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = _parents, \ + .clkdef.parent_cnt = nitems(_parents), \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .cmd_rcgr = _rcgr, \ + .hid_width = _hid_width, \ + .mnd_width = _mnd_width, \ + .safe_src_idx = _safe_src_idx, \ + .flags= _flags, \ + .safe_pre_parent_idx = _safe_pre_parent_idx, \ + .freq_tbl = _freq_tbl, \ +} + +/* branch2 gate nodes */ +#define F_BRANCH2(_id, _cname, _parent, _eo, _es, _hr, _hs, _haltreg, \ + _type, _voted, _flags) \ +{ \ + .clkdef.id = _id, \ + .clkdef.name = _cname, \ + .clkdef.parent_names = (const char *[]){_parent}, \ + .clkdef.parent_cnt = 1, \ + .clkdef.flags = CLK_NODE_STATIC_STRINGS, \ + .enable_offset = _eo, \ + .enable_shift = _es, \ + .hwcg_reg = _hr, \ + .hwcg_bit = _hs, \ + .halt_reg = _haltreg, \ + .halt_check_type = _type, \ + .halt_check_voted = _voted, \ + .flags = _flags, \ +} + +/* + * Fixed "gcc_fepll_vco" PLL derived sources: + * + * P_FEPLL125 - 125MHz + * P_FEPLL125DLY - 125MHz + * P_FEPLL200 - 200MHz + * "fepll500" - 500MHz + * + * Fixed "gcc_apps_ddrpll_vco" PLL derived sources: + * + * P_DDRPLL - 192MHz + */ +static struct qcom_clk_fdiv_def fdiv_tbl[] = { + F_FDIV(GCC_FEPLL125_CLK, "fepll125", "gcc_fepll_vco", 32), + F_FDIV(GCC_FEPLL125DLY_CLK, "fepll125dly", "gcc_fepll_vco", 32), + F_FDIV(GCC_FEPLL200_CLK, "fepll200", "gcc_fepll_vco", 20), + F_FDIV(GCC_FEPLL500_CLK, "fepll500", "gcc_fepll_vco", 8), + F_FDIV(GCC_SDCC_PLLDIV_CLK, "ddrpllsdcc", "gcc_apps_ddrpll_vco", 28), +}; + +/* + * FEPLL - 48MHz (xo) input, 4GHz output + * DDRPLL - 48MHz (xo) input, 5.376GHz output + */ +static struct qcom_clk_fepll_def fepll_tbl[] = { + F_FEPLL(GCC_FEPLL_VCO, "gcc_fepll_vco", "xo", 0x2f020, 16, 8, 24, 5), + F_FEPLL(GCC_APSS_DDRPLL_VCO, "gcc_apps_ddrpll_vco", "xo", 0x2e020, + 16, 8, 24, 5), +}; + +/* + * Frequency table for the APSS PLL/DIV path for the CPU frequency. + * + * Note - the APSS DIV code only needs the frequency and pre-divisor, + * not the other fields. + */ +static struct qcom_clk_freq_tbl apss_freq_tbl[] = { + { 384000000, "gcc_apps_ddrpll_vco", 0xd, 0, 0 }, + { 413000000, "gcc_apps_ddrpll_vco", 0xc, 0, 0 }, + { 448000000, "gcc_apps_ddrpll_vco", 0xb, 0, 0 }, + { 488000000, "gcc_apps_ddrpll_vco", 0xa, 0, 0 }, + { 512000000, "gcc_apps_ddrpll_vco", 0x9, 0, 0 }, + { 537000000, "gcc_apps_ddrpll_vco", 0x8, 0, 0 }, + { 565000000, "gcc_apps_ddrpll_vco", 0x7, 0, 0 }, + { 597000000, "gcc_apps_ddrpll_vco", 0x6, 0, 0 }, + { 632000000, "gcc_apps_ddrpll_vco", 0x5, 0, 0 }, + { 672000000, "gcc_apps_ddrpll_vco", 0x4, 0, 0 }, + { 716000000, "gcc_apps_ddrpll_vco", 0x3, 0, 0 }, + { 768000000, "gcc_apps_ddrpll_vco", 0x2, 0, 0 }, + { 823000000, "gcc_apps_ddrpll_vco", 0x1, 0, 0 }, + { 896000000, "gcc_apps_ddrpll_vco", 0x0, 0, 0 }, + { 0, } +}; + +/* + * APSS div/gate + */ +static struct qcom_clk_apssdiv_def apssdiv_tbl[] = { + F_APSSDIV(GCC_APSS_CPU_PLLDIV_CLK, "ddrpllapss", + "gcc_apps_ddrpll_vco", 0x2e020, + 4, 4, 0x2e000, 0, &apss_freq_tbl[0]), +}; + +/* + * Parent clocks for the apps_clk_src clock. + */ +static const char * apps_clk_src_parents[] = { + "xo", "ddrpllapss", "fepll500", "fepll200" +}; + +/* + * Parents lists for a variety of blocks. + */ +static const char * gcc_xo_200_parents[] = { + "xo", "fepll200" +}; +static const char * gcc_xo_200_500_parents[] = { + "xo", "fepll200", "fepll500" +}; +static const char * gcc_xo_200_spi_parents[] = { + "xo", NULL, "fepll200" +}; +static const char * gcc_xo_sdcc1_500_parents[] = { + "xo", "ddrpllsdcc", "fepll500" +}; + +static const char * gcc_xo_125_dly_parents[] = { + "xo", "fepll125dly" +}; + +static const char * gcc_xo_wcss2g_parents[] = { + "xo", "fepllwcss2g" +}; + +static const char * gcc_xo_wcss5g_parents[] = { + "xo", "fepllwcss5g" +}; + +static struct qcom_clk_freq_tbl apps_clk_src_freq_tbl[] = { + { 48000000, "xo", 1, 0, 0 }, + { 200000000, "fepll200", 1, 0, 0 }, + { 384000000, "ddrpllapss", 1, 0, 0 }, + { 413000000, "ddrpllapss", 1, 0, 0 }, + { 448000000, "ddrpllapss", 1, 0, 0 }, + { 488000000, "ddrpllapss", 1, 0, 0 }, + { 500000000, "fepll500", 1, 0, 0 }, + { 512000000, "ddrpllapss", 1, 0, 0 }, + { 537000000, "ddrpllapss", 1, 0, 0 }, + { 565000000, "ddrpllapss", 1, 0, 0 }, + { 597000000, "ddrpllapss", 1, 0, 0 }, + { 632000000, "ddrpllapss", 1, 0, 0 }, + { 672000000, "ddrpllapss", 1, 0, 0 }, + { 716000000, "ddrpllapss", 1, 0, 0 }, + { 0,} + +}; + +static struct qcom_clk_freq_tbl audio_clk_src_freq_tbl[] = { + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 200000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0,} +}; + +static struct qcom_clk_freq_tbl blsp1_qup1_i2c_apps_clk_src_freq_tbl[] = { + { 19050000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(10.5), 1, 1 }, + { 0,} +}; + +static struct qcom_clk_freq_tbl blsp1_qup1_spi_apps_clk_src_freq_tbl[] = { + { 960000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(12), 1, 4 }, + { 4800000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 10 }, + { 9600000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 5 }, + { 15000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 3 }, + { 19200000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 2, 5 }, + { 24000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 2 }, + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0,} +}; + +static struct qcom_clk_freq_tbl gcc_pcnoc_ahb_clk_src_freq_tbl[] = { + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 100000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(2), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl blsp1_uart1_apps_clk_src_freq_tbl[] = { + { 1843200, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 144, 15625 }, + { 3686400, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 288, 15625 }, + { 7372800, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 576, 15625 }, + { 14745600, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1152, 15625 }, + { 16000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 2, 25 }, + { 24000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 2 }, + { 32000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 4, 25 }, + { 40000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 5 }, + { 46400000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 29, 125 }, + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl gp1_clk_src_freq_tbl[] = { + { 1250000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 16, 0 }, + { 2500000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 8, 0 }, + { 5000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 4, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl sdcc1_apps_clk_src_freq_tbl[] = { + { 144000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 3, 240 }, + { 400000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 0 }, + { 20000000, "fepll500", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 25 }, + { 25000000, "fepll500", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 20 }, + { 50000000, "fepll500", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 10 }, + { 100000000, "fepll500", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 1, 5 }, + { 192000000, "ddrpllsdcc", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl apps_ahb_clk_src_freq_tbl[] = { + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 100000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(2), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl usb30_mock_utmi_clk_src_freq_tbl[] = { + { 2000000, "fepll200", QCOM_CLK_FREQTBL_PREDIV_RCG2(10), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl fephy_125m_dly_clk_src_freq_tbl[] = { + { 125000000, "fepll125dly", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl wcss2g_clk_src_freq_tbl[] = { + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 250000000, "fepllwcss2g", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0, } +}; + +static struct qcom_clk_freq_tbl wcss5g_clk_src_freq_tbl[] = { + { 48000000, "xo", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 250000000, "fepllwcss5g", QCOM_CLK_FREQTBL_PREDIV_RCG2(1), 0, 0 }, + { 0, } +}; + +/* + * Divisor table for the 2g/5g wifi clock divisors. + */ +static struct qcom_clk_ro_div_tbl fepllwcss_clk_div_tbl[] = { + { 0, 15 }, + { 1, 16 }, + { 2, 18 }, + { 3, 20 }, + { 0, 0 } +}; + +/* + * Read-only divisor table clocks. + */ +static struct qcom_clk_ro_div_def ro_div_tbl[] = { + F_RO_DIV(GCC_FEPLL_WCSS2G_CLK, "fepllwcss2g", "gcc_fepll_vco", + 0x2f020, 8, 2, &fepllwcss_clk_div_tbl[0]), + F_RO_DIV(GCC_FEPLL_WCSS5G_CLK, "fepllwcss5g", "gcc_fepll_vco", + 0x2f020, 12, 2, &fepllwcss_clk_div_tbl[0]), +}; + +/* + * RCG2 clocks + */ +static struct qcom_clk_rcg2_def rcg2_tbl[] = { + F_RCG2(AUDIO_CLK_SRC, "audio_clk_src", gcc_xo_200_parents, + 0x1b000, 5, 0, -1, -1, 0, 0, &audio_clk_src_freq_tbl[0]), + F_RCG2(BLSP1_QUP1_I2C_APPS_CLK_SRC, "blsp1_qup1_i2c_apps_clk_src", + gcc_xo_200_parents, 0x200c, 5, 0, -1, -1, 0, 0, + &blsp1_qup1_i2c_apps_clk_src_freq_tbl[0]), + F_RCG2(BLSP1_QUP2_I2C_APPS_CLK_SRC, "blsp1_qup2_i2c_apps_clk_src", + gcc_xo_200_parents, 0x3000, 5, 0, -1, -1, 0, 0, + &blsp1_qup1_i2c_apps_clk_src_freq_tbl[0]), + F_RCG2(BLSP1_QUP1_SPI_APPS_CLK_SRC, "blsp1_qup1_spi_apps_clk_src", + gcc_xo_200_spi_parents, 0x2024, 5, 8, -1, -1, 0, 0, + &blsp1_qup1_spi_apps_clk_src_freq_tbl[0]), + F_RCG2(BLSP1_QUP2_SPI_APPS_CLK_SRC, "blsp1_qup2_spi_apps_clk_src", + gcc_xo_200_spi_parents, 0x3014, 5, 8, -1, -1, 0, 0, + &blsp1_qup1_spi_apps_clk_src_freq_tbl[0]), + F_RCG2(BLSP1_UART1_APPS_CLK_SRC, "blsp1_uart1_apps_clk_src", + gcc_xo_200_spi_parents, 0x2044, 5, 16, -1, -1, 0, 0, + &blsp1_uart1_apps_clk_src_freq_tbl[0]), + F_RCG2(BLSP1_UART2_APPS_CLK_SRC, "blsp1_uart2_apps_clk_src", + gcc_xo_200_spi_parents, 0x3034, 5, 16, -1, -1, 0, 0, + &blsp1_uart1_apps_clk_src_freq_tbl[0]), + F_RCG2(GP1_CLK_SRC, "gp1_clk_src", gcc_xo_200_parents, 0x8004, + 5, 8, -1, -1, 0, 0, + &gp1_clk_src_freq_tbl[0]), + F_RCG2(GP2_CLK_SRC, "gp2_clk_src", gcc_xo_200_parents, 0x9004, + 5, 8, -1, -1, 0, 0, + &gp1_clk_src_freq_tbl[0]), + F_RCG2(GP3_CLK_SRC, "gp3_clk_src", gcc_xo_200_parents, 0xa004, + 5, 8, -1, -1, 0, 0, + &gp1_clk_src_freq_tbl[0]), + F_RCG2(SDCC1_APPS_CLK_SRC, "sdcc1_apps_clk_src", + gcc_xo_sdcc1_500_parents, 0x18004, 5, 0, -1, -1, 0, 0, + &sdcc1_apps_clk_src_freq_tbl[0]), + F_RCG2(GCC_APPS_CLK_SRC, "apps_clk_src", apps_clk_src_parents, + 0x1900c, 5, 0, -1, 2, 0, + QCOM_CLK_RCG2_FLAGS_SET_RATE_PARENT, + &apps_clk_src_freq_tbl[0]), + F_RCG2(GCC_APPS_AHB_CLK_SRC, "apps_ahb_clk_src", + gcc_xo_200_500_parents, 0x19014, 5, 0, -1, -1, 0, + 0, &apps_ahb_clk_src_freq_tbl[0]), + F_RCG2(GCC_USB3_MOCK_UTMI_CLK_SRC, "usb30_mock_utmi_clk_src", + gcc_xo_200_parents, 0x1e000, 5, 0, -1, -1, 0, 0, + &usb30_mock_utmi_clk_src_freq_tbl[0]), + F_RCG2(FEPHY_125M_DLY_CLK_SRC, "fephy_125m_dly_clk_src", + gcc_xo_125_dly_parents, 0x12000, 5, 0, -1, -1, 0, 0, + &fephy_125m_dly_clk_src_freq_tbl[0]), + F_RCG2(WCSS2G_CLK_SRC, "wcss2g_clk_src", gcc_xo_wcss2g_parents, + 0x1f000, 5, 0, -1, -1, 0, 0, + &wcss2g_clk_src_freq_tbl[0]), + F_RCG2(WCSS5G_CLK_SRC, "wcss5g_clk_src", gcc_xo_wcss5g_parents, + 0x20000, 5, 0, -1, -1, 0, 0, + &wcss5g_clk_src_freq_tbl[0]), + F_RCG2(GCC_PCNOC_AHB_CLK_SRC, "gcc_pcnoc_ahb_clk_src", + gcc_xo_200_500_parents, 0x21024, 5, 0, -1, -1, 0, 0, + &gcc_pcnoc_ahb_clk_src_freq_tbl[0]), +}; + +/* + * branch2 clocks + */ +static struct qcom_clk_branch2_def branch2_tbl[] = { + F_BRANCH2(GCC_AUDIO_AHB_CLK, "gcc_audio_ahb_clk", "pcnoc_clk_src", + 0x1b010, 0, 0, 0, 0x1b010, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_AUDIO_PWM_CLK, "gcc_audio_pwm_clk", "audio_clk_src", + 0x1b00c, 0, 0, 0, 0x1b00c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_QUP1_I2C_APPS_CLK, "gcc_blsp1_qup1_i2c_apps_clk", + "blsp1_qup1_i2c_apps_clk_src", + 0x2008, 0, 0, 0, 0x2008, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_QUP2_I2C_APPS_CLK, "gcc_blsp1_qup2_i2c_apps_clk", + "blsp1_qup2_i2c_apps_clk_src", + 0x3010, 0, 0, 0, 0x3010, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_QUP1_SPI_APPS_CLK, "gcc_blsp1_qup1_spi_apps_clk", + "blsp1_qup1_spi_apps_clk_src", + 0x2004, 0, 0, 0, 0x2004, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_QUP2_SPI_APPS_CLK, "gcc_blsp1_qup2_spi_apps_clk", + "blsp1_qup2_spi_apps_clk_src", + 0x300c, 0, 0, 0, 0x300c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_UART1_APPS_CLK, "gcc_blsp1_uart1_apps_clk", + "blsp1_uart1_apps_clk_src", + 0x203c, 0, 0, 0, 0x203c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_UART2_APPS_CLK, "gcc_blsp1_uart2_apps_clk", + "blsp1_uart2_apps_clk_src", + 0x302c, 0, 0, 0, 0x302c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_GP1_CLK, "gcc_gp1_clk", "gp1_clk_src", + 0x8000, 0, 0, 0, 0x8000, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_GP2_CLK, "gcc_gp2_clk", "gp2_clk_src", + 0x9000, 0, 0, 0, 0x9000, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_GP3_CLK, "gcc_gp3_clk", "gp3_clk_src", + 0xa000, 0, 0, 0, 0xa000, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + /* BRANCH_HALT_VOTED; note the different enable/halt */ + F_BRANCH2(GCC_APPS_AHB_CLK_SRC, "gcc_apss_ahb_clk", + "apps_ahb_clk_src", + 0x6000, 14, 0, 0, 0x19004, QCOM_CLK_BRANCH2_BRANCH_HALT, + true, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_BLSP1_AHB_CLK, "gcc_blsp1_ahb_clk", + "pcnoc_clk_src", + 0x6000, 10, 0, 0, 0x1008, QCOM_CLK_BRANCH2_BRANCH_HALT, + true, 0), /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_DCD_XO_CLK, "gcc_dcd_xo_clk", "xo", + 0x2103c, 0, 0, 0, 0x2103c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_BOOT_ROM_AHB_CLK, "gcc_boot_rom_ahb_clk", + "pcnoc_clk_src", 0x1300c, 0, 0, 0, 0x1300c, + QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_CRYPTO_AHB_CLK, "gcc_crypto_ahb_clk", + "pcnoc_clk_src", 0x6000, 0, 0, 0, 0x16024, + QCOM_CLK_BRANCH2_BRANCH_HALT, + true, 0), /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_CRYPTO_AXI_CLK, "gcc_crypto_axi_clk", + "fepll125", 0x6000, 1, 0, 0, 0x16020, + QCOM_CLK_BRANCH2_BRANCH_HALT, + true, 0), /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_CRYPTO_CLK, "gcc_crypto_clk", "fepll125", + 0x6000, 2, 0, 0, 0x1601c, QCOM_CLK_BRANCH2_BRANCH_HALT, + true, 0), /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_ESS_CLK, "gcc_ess_clk", "fephy_125m_dly_clk_src", + 0x12010, 0, 0, 0, 0x12010, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_IMEM_AXI_CLK, "gcc_imem_axi_clk", "fepll200", + 0x6000, 17, 0, 0, 0xe004, QCOM_CLK_BRANCH2_BRANCH_HALT, + true, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_IMEM_CFG_AHB_CLK, "gcc_imem_cfg_ahb_clk", + "pcnoc_clk_src", + 0xe008, 0, 0, 0, 0xe008, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_PCIE_AHB_CLK, "gcc_pcie_ahb_clk", "pcnoc_clk_src", + 0x1d00c, 0, 0, 0, 0x1d00c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_PCIE_AXI_M_CLK, "gcc_pcie_axi_m_clk", "fepll200", + 0x1d004, 0, 0, 0, 0x1d004, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_PCIE_AXI_S_CLK, "gcc_pcie_axi_s_clk", "fepll200", + 0x1d008, 0, 0, 0, 0x1d008, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_PRNG_AHB_CLK, "gcc_prng_ahb_clk", "pcnoc_clk_src", + 0x6000, 8, 0, 0, 0x13004, QCOM_CLK_BRANCH2_BRANCH_HALT, + true, 0), /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_QPIC_AHB_CLK, "gcc_qpic_ahb_clk", "pcnoc_clk_src", + 0x1c008, 0, 0, 0, 0x1c008, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_QPIC_CLK, "gcc_qpic_clk", "pcnoc_clk_src", + 0x1c004, 0, 0, 0, 0x1c004, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_SDCC1_AHB_CLK, "gcc_sdcc1_ahb_clk", "pcnoc_clk_src", + 0x18010, 0, 0, 0, 0x18010, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_SDCC1_APPS_CLK, "gcc_sdcc1_apps_clk", + "sdcc1_apps_clk_src", 0x1800c, 0, 0, 0, 0x1800c, + QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_TLMM_AHB_CLK, "gcc_tlmm_ahb_clk", "pcnoc_clk_src", + 0x6000, 5, 0, 0, 0x5004, QCOM_CLK_BRANCH2_BRANCH_HALT, + true, 0), /* BRANCH_HALT_VOTED */ + F_BRANCH2(GCC_USB2_MASTER_CLK, "gcc_usb2_master_clk", "pcnoc_clk_src", + 0x1e00c, 0, 0, 0, 0x1e00c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_USB2_SLEEP_CLK, "gcc_usb2_sleep_clk", + "sleep_clk", 0x1e010, 0, 0, 0, 0x1e010, + QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_USB2_MOCK_UTMI_CLK, "gcc_usb2_mock_utmi_clk", + "usb30_mock_utmi_clk_src", 0x1e014, 0, 0, 0, 0x1e014, + QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_USB3_MASTER_CLK, "gcc_usb3_master_clk", "fepll125", + 0x1e028, 0, 0, 0, 0x1e028, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_USB3_SLEEP_CLK, "gcc_usb3_sleep_clk", "sleep_clk", + 0x1e02c, 0, 0, 0, 0x1e02c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + F_BRANCH2(GCC_USB3_MOCK_UTMI_CLK, "gcc_usb3_mock_utmi_clk", + "usb30_mock_utmi_clk_src", + 0x1e030, 0, 0, 0, 0x1e030, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + /* Note - yes, these two have the same registers in linux */ + F_BRANCH2(GCC_WCSS2G_CLK, "gcc_wcss2g_clk", "wcss2g_clk_src", + 0x1f00c, 0, 0, 0, 0x1f00c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_WCSS2G_REF_CLK, "gcc_wcss2g_ref_clk", "xo", + 0x1f00c, 0, 0, 0, 0x1f00c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_WCSS2G_RTC_CLK, "gcc_wcss2g_rtc_clk", "sleep_clk", + 0x1f010, 0, 0, 0, 0x1f010, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + + /* Note - yes, these two have the same registers in linux */ + F_BRANCH2(GCC_WCSS5G_CLK, "gcc_wcss5g_clk", "wcss5g_clk_src", + 0x1f00c, 0, 0, 0, 0x2000c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_WCSS5G_REF_CLK, "gcc_wcss5g_ref_clk", "xo", + 0x1f00c, 0, 0, 0, 0x2000c, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), + F_BRANCH2(GCC_WCSS5G_RTC_CLK, "gcc_wcss5g_rtc_clk", "sleep_clk", + 0x1f010, 0, 0, 0, 0x20010, QCOM_CLK_BRANCH2_BRANCH_HALT, + false, 0), + + F_BRANCH2(GCC_PCNOC_AHB_CLK, "pcnoc_clk_src", "gcc_pcnoc_ahb_clk_src", + 0x21030, 0, 0, 0, 0x21030, QCOM_CLK_BRANCH2_BRANCH_HALT, false, + QCOM_CLK_BRANCH2_FLAGS_CRITICAL | + QCOM_CLK_BRANCH2_FLAGS_SET_RATE_PARENT), +}; + +static void +qcom_gcc_ipq4018_clock_init_fepll(struct qcom_gcc_ipq4018_softc *sc) +{ + int i, rv; + + for (i = 0; i < nitems(fepll_tbl); i++) { + rv = qcom_clk_fepll_register(sc->clkdom, fepll_tbl + i); + if (rv != 0) + panic("qcom_clk_fepll_register failed"); + } +} + +static void +qcom_gcc_ipq4018_clock_init_fdiv(struct qcom_gcc_ipq4018_softc *sc) +{ + int i, rv; + + for (i = 0; i < nitems(fdiv_tbl); i++) { + rv = qcom_clk_fdiv_register(sc->clkdom, fdiv_tbl + i); + if (rv != 0) + panic("qcom_clk_fdiv_register failed"); + } +} + +static void +qcom_gcc_ipq4018_clock_init_apssdiv(struct qcom_gcc_ipq4018_softc *sc) +{ + int i, rv; + + for (i = 0; i < nitems(apssdiv_tbl); i++) { + rv = qcom_clk_apssdiv_register(sc->clkdom, apssdiv_tbl + i); + if (rv != 0) + panic("qcom_clk_apssdiv_register failed"); + } +} + +static void +qcom_gcc_ipq4018_clock_init_rcg2(struct qcom_gcc_ipq4018_softc *sc) +{ + int i, rv; + + for (i = 0; i < nitems(rcg2_tbl); i++) { + rv = qcom_clk_rcg2_register(sc->clkdom, rcg2_tbl + i); + if (rv != 0) + panic("qcom_clk_rcg2_register failed"); + } +} + +static void +qcom_gcc_ipq4018_clock_init_branch2(struct qcom_gcc_ipq4018_softc *sc) +{ + int i, rv; + + for (i = 0; i < nitems(branch2_tbl); i++) { + rv = qcom_clk_branch2_register(sc->clkdom, branch2_tbl + i); + if (rv != 0) + panic("qcom_clk_branch2_register failed"); + } +} + +static void +qcom_gcc_ipq4018_clock_init_ro_div(struct qcom_gcc_ipq4018_softc *sc) +{ + int i, rv; + + for (i = 0; i < nitems(ro_div_tbl); i++) { + rv = qcom_clk_ro_div_register(sc->clkdom, ro_div_tbl + i); + if (rv != 0) + panic("qcom_clk_ro_div_register failed"); + } +} + +int +qcom_gcc_ipq4018_clock_read(device_t dev, bus_addr_t addr, uint32_t *val) +{ + struct qcom_gcc_ipq4018_softc *sc; + + sc = device_get_softc(dev); + *val = bus_read_4(sc->reg, addr); + return (0); +} + +int +qcom_gcc_ipq4018_clock_write(device_t dev, bus_addr_t addr, uint32_t val) +{ + struct qcom_gcc_ipq4018_softc *sc; + + sc = device_get_softc(dev); + bus_write_4(sc->reg, addr, val); + return (0); +} + +int +qcom_gcc_ipq4018_clock_modify(device_t dev, bus_addr_t addr, + uint32_t clear_mask, uint32_t set_mask) +{ + struct qcom_gcc_ipq4018_softc *sc; + uint32_t reg; + + sc = device_get_softc(dev); + reg = bus_read_4(sc->reg, addr); + reg &= clear_mask; + reg |= set_mask; + bus_write_4(sc->reg, addr, reg); + return (0); +} + +void +qcom_gcc_ipq4018_clock_setup(struct qcom_gcc_ipq4018_softc *sc) +{ + + sc->clkdom = clkdom_create(sc->dev); + + /* Setup stuff */ + qcom_gcc_ipq4018_clock_init_fepll(sc); + qcom_gcc_ipq4018_clock_init_fdiv(sc); + qcom_gcc_ipq4018_clock_init_apssdiv(sc); + qcom_gcc_ipq4018_clock_init_rcg2(sc); + qcom_gcc_ipq4018_clock_init_branch2(sc); + qcom_gcc_ipq4018_clock_init_ro_div(sc); + + /* Finalise clock tree */ + clkdom_finit(sc->clkdom); +} + +void +qcom_gcc_ipq4018_clock_lock(device_t dev) +{ + struct qcom_gcc_ipq4018_softc *sc; + + sc = device_get_softc(dev); + mtx_lock(&sc->mtx); +} + +void +qcom_gcc_ipq4018_clock_unlock(device_t dev) +{ + struct qcom_gcc_ipq4018_softc *sc; + + sc = device_get_softc(dev); + mtx_unlock(&sc->mtx); +} Index: sys/dev/qcom_gcc/qcom_gcc_ipq4018_reset.c =================================================================== --- sys/dev/qcom_gcc/qcom_gcc_ipq4018_reset.c +++ sys/dev/qcom_gcc/qcom_gcc_ipq4018_reset.c @@ -53,7 +53,7 @@ #include -#include +#include "qcom_gcc_ipq4018_var.h" static const struct qcom_gcc_ipq4018_reset_entry gcc_ipq4019_reset_list[] = { @@ -143,7 +143,6 @@ return (EINVAL); } - device_printf(dev, "%s: called; id=%d, reset=%d\n", __func__, id, reset); mtx_lock(&sc->mtx); reg = bus_read_4(sc->reg, gcc_ipq4019_reset_list[id].reg); if (reset) Index: sys/dev/qcom_gcc/qcom_gcc_ipq4018_var.h =================================================================== --- sys/dev/qcom_gcc/qcom_gcc_ipq4018_var.h +++ sys/dev/qcom_gcc/qcom_gcc_ipq4018_var.h @@ -40,11 +40,28 @@ int reg_rid; struct resource *reg; struct mtx mtx; + struct clkdom *clkdom; }; +/* + * reset block + */ extern int qcom_gcc_ipq4018_hwreset_assert(device_t dev, intptr_t id, bool reset); extern int qcom_gcc_ipq4018_hwreset_is_asserted(device_t dev, intptr_t id, bool *reset); +/* + * clock block + */ +extern int qcom_gcc_ipq4018_clock_read(device_t dev, bus_addr_t addr, + uint32_t *val); +extern int qcom_gcc_ipq4018_clock_write(device_t dev, bus_addr_t addr, + uint32_t val); +extern int qcom_gcc_ipq4018_clock_modify(device_t dev, bus_addr_t addr, + uint32_t clear_mask, uint32_t set_mask); +extern void qcom_gcc_ipq4018_clock_setup(struct qcom_gcc_ipq4018_softc *sc); +extern void qcom_gcc_ipq4018_clock_lock(device_t dev); +extern void qcom_gcc_ipq4018_clock_unlock(device_t dev); + #endif /* __QCOM_GCC_IPQ4018_VAR_H__ */