/* $NetBSD: jh71x0_clkc.c,v 1.2 2024/08/04 08:16:26 skrll Exp $ */ /*- * Copyright (c) 2023 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Nick Hudson * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 __KERNEL_RCSID(0, "$NetBSD: jh71x0_clkc.c,v 1.2 2024/08/04 08:16:26 skrll Exp $"); #include #include #include #include #include #include #define RD4(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define WR4(sc, reg, val) \ bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) static void jh71x0_clkc_update(struct jh71x0_clkc_softc * const sc, struct jh71x0_clkc_clk *jcc, uint32_t set, uint32_t clr) { // lock uint32_t val = RD4(sc, jcc->jcc_reg); val &= ~clr; val |= set; WR4(sc, jcc->jcc_reg, val); } /* * FIXED_FACTOR operations */ static u_int jh71x0_clkc_fixed_factor_get_parent_rate(struct clk *clk) { struct clk *clk_parent = clk_get_parent(clk); if (clk_parent == NULL) return 0; return clk_get_rate(clk_parent); } u_int jh71x0_clkc_fixed_factor_get_rate(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_FIXED_FACTOR); struct jh71x0_clkc_fixed_factor * const jcff = &jcc->jcc_ffactor; struct clk *clk = &jcc->jcc_clk; uint64_t rate = jh71x0_clkc_fixed_factor_get_parent_rate(clk); if (rate == 0) return 0; rate *= jcff->jcff_mult; rate /= jcff->jcff_div; return rate; } static int jh71x0_clkc_fixed_factor_set_parent_rate(struct clk *clk, u_int rate) { struct clk *clk_parent = clk_get_parent(clk); if (clk_parent == NULL) return ENXIO; return clk_set_rate(clk_parent, rate); } int jh71x0_clkc_fixed_factor_set_rate(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc, u_int rate) { KASSERT(jcc->jcc_type == JH71X0CLK_FIXED_FACTOR); struct jh71x0_clkc_fixed_factor * const jcff = &jcc->jcc_ffactor; struct clk *clk = &jcc->jcc_clk; uint64_t tmp = rate; tmp *= jcff->jcff_div; tmp /= jcff->jcff_mult; return jh71x0_clkc_fixed_factor_set_parent_rate(clk, tmp); } const char * jh71x0_clkc_fixed_factor_get_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_FIXED_FACTOR); struct jh71x0_clkc_fixed_factor * const jcff = &jcc->jcc_ffactor; return jcff->jcff_parent; } /* * MUX operations */ int jh71x0_clkc_mux_set_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc, const char *name) { KASSERT(jcc->jcc_type == JH71X0CLK_MUX); struct jh71x0_clkc_mux * const jcm = &jcc->jcc_mux; size_t i; for (i = 0; i < jcm->jcm_nparents; i++) { if (jcm->jcm_parents[i] != NULL && strcmp(jcm->jcm_parents[i], name) == 0) break; } if (i >= jcm->jcm_nparents) return EINVAL; uint32_t val = RD4(sc, jcc->jcc_reg); val &= ~JH71X0_CLK_MUX_MASK; val |= __SHIFTIN(i, JH71X0_CLK_MUX_MASK); WR4(sc, jcc->jcc_reg, val); return 0; } const char * jh71x0_clkc_mux_get_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_MUX); uint32_t val = RD4(sc, jcc->jcc_reg); size_t pindex = __SHIFTOUT(val, JH71X0_CLK_MUX_MASK); if (pindex >= jcc->jcc_mux.jcm_nparents) return NULL; return jcc->jcc_mux.jcm_parents[pindex]; } /* * GATE operations */ int jh71x0_clkc_gate_enable(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc, int enable) { KASSERT(jcc->jcc_type == JH71X0CLK_GATE); jh71x0_clkc_update(sc, jcc, (enable ? JH71X0_CLK_ENABLE : 0), JH71X0_CLK_ENABLE); return 0; } const char * jh71x0_clkc_gate_get_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_GATE); struct jh71x0_clkc_gate *jcc_gate = &jcc->jcc_gate; return jcc_gate->jcg_parent; } /* * DIVIDER operations */ u_int jh71x0_clkc_div_get_rate(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_DIV); struct clk * const clk = &jcc->jcc_clk; struct clk * const clk_parent = clk_get_parent(clk); if (clk_parent == NULL) return 0; u_int rate = clk_get_rate(clk_parent); if (rate == 0) return 0; uint32_t val = RD4(sc, jcc->jcc_reg); uint32_t div = __SHIFTOUT(val, JH71X0_CLK_DIV_MASK); return rate / div; } int jh71x0_clkc_div_set_rate(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc, u_int new_rate) { KASSERT(jcc->jcc_type == JH71X0CLK_DIV); struct jh71x0_clkc_div * const jcc_div = &jcc->jcc_div; struct clk * const clk = &jcc->jcc_clk; struct clk * const clk_parent = clk_get_parent(clk); if (clk_parent == NULL) return ENXIO; if (jcc_div->jcd_maxdiv == 0) return ENXIO; u_int parent_rate = clk_get_rate(clk_parent); if (parent_rate == 0) { return (new_rate == 0) ? 0 : ERANGE; } u_int ratio = howmany(parent_rate, new_rate); u_int div = uimin(ratio, jcc_div->jcd_maxdiv); jh71x0_clkc_update(sc, jcc, __SHIFTIN(div, JH71X0_CLK_DIV_MASK), JH71X0_CLK_DIV_MASK); return 0; } const char * jh71x0_clkc_div_get_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_DIV); struct jh71x0_clkc_div *jcc_div = &jcc->jcc_div; return jcc_div->jcd_parent; } /* * FRACTIONAL DIVIDER operations */ u_int jh71x0_clkc_fracdiv_get_rate(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_FRACDIV); struct clk * const clk = &jcc->jcc_clk; struct clk * const clk_parent = clk_get_parent(clk); if (clk_parent == NULL) return 0; u_int rate = clk_get_rate(clk_parent); if (rate == 0) return 0; uint32_t val = RD4(sc, jcc->jcc_reg); unsigned long div100 = 100UL * __SHIFTOUT(val, JH71X0_CLK_INT_MASK) + __SHIFTOUT(val, JH71X0_CLK_FRAC_MASK); return (div100 >= JH71X0_CLK_FRAC_MIN) ? 100UL * rate / div100 : 0; } int jh71x0_clkc_fracdiv_set_rate(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc, u_int new_rate) { KASSERT(jcc->jcc_type == JH71X0CLK_FRACDIV); // struct jh71x0_clkc_fracdiv * const jcc_fracdiv = &jcc->jcc_fracdiv; struct clk * const clk = &jcc->jcc_clk; struct clk * const clk_parent = clk_get_parent(clk); if (clk_parent == NULL) return ENXIO; #if 0 if (jcc_div->jcd_maxdiv == 0) return ENXIO; u_int parent_rate = clk_get_rate(clk_parent); if (parent_rate == 0) { return (new_rate == 0) ? 0 : ERANGE; } u_int ratio = howmany(parent_rate, new_rate); u_int div = uimin(ratio, jcc_div->jcd_maxdiv); jh71x0_clkc_update(sc, jcc, __SHIFTIN(div, JH71X0_CLK_DIV_MASK), JH71X0_CLK_DIV_MASK); #endif return 0; } const char * jh71x0_clkc_fracdiv_get_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_FRACDIV); struct jh71x0_clkc_fracdiv *jcc_fracdiv = &jcc->jcc_fracdiv; return jcc_fracdiv->jcd_parent; } /* * INV operations */ const char * jh71x0_clkc_inv_get_parent(struct jh71x0_clkc_softc *sc, struct jh71x0_clkc_clk *jcc) { KASSERT(jcc->jcc_type == JH71X0CLK_INV); struct jh71x0_clkc_inv * const jci = &jcc->jcc_inv; return jci->jci_parent; } struct jh71x0_clkc_clkops jh71x0_clkc_gate_ops = { .jcco_enable = jh71x0_clkc_gate_enable, .jcco_getparent = jh71x0_clkc_gate_get_parent, }; struct jh71x0_clkc_clkops jh71x0_clkc_div_ops = { .jcco_setrate = jh71x0_clkc_div_set_rate, .jcco_getrate = jh71x0_clkc_div_get_rate, .jcco_getparent = jh71x0_clkc_div_get_parent, }; struct jh71x0_clkc_clkops jh71x0_clkc_fracdiv_ops = { .jcco_setrate = jh71x0_clkc_fracdiv_set_rate, .jcco_getrate = jh71x0_clkc_fracdiv_get_rate, .jcco_getparent = jh71x0_clkc_fracdiv_get_parent, }; struct jh71x0_clkc_clkops jh71x0_clkc_ffactor_ops = { .jcco_setrate = jh71x0_clkc_fixed_factor_set_rate, .jcco_getrate = jh71x0_clkc_fixed_factor_get_rate, .jcco_getparent = jh71x0_clkc_fixed_factor_get_parent, }; struct jh71x0_clkc_clkops jh71x0_clkc_mux_ops = { .jcco_setparent = jh71x0_clkc_mux_set_parent, .jcco_getparent = jh71x0_clkc_mux_get_parent, }; struct jh71x0_clkc_clkops jh71x0_clkc_inv_ops = { .jcco_getparent = jh71x0_clkc_inv_get_parent, }; static struct clk * jh71x0_clkc_get(void *priv, const char *name) { struct jh71x0_clkc_softc * const sc = priv; for (u_int id = 0; id < sc->sc_nclks; id++) { struct jh71x0_clkc_clk * const jcc = &sc->sc_clk[id]; if (strcmp(name, jcc->jcc_clk.name) == 0) { return &jcc->jcc_clk; } } return NULL; } static void jh71x0_clkc_put(void *priv, struct clk *clk) { } static int jh71x0_clkc_set_rate(void *priv, struct clk *clk, u_int rate) { struct jh71x0_clkc_softc * const sc = priv; struct jh71x0_clkc_clk * const jcc = container_of(clk, struct jh71x0_clkc_clk, jcc_clk); if (clk->flags & CLK_SET_RATE_PARENT) { struct clk *clk_parent = clk_get_parent(clk); if (clk_parent == NULL) { aprint_debug("%s: no parent for %s\n", __func__, jcc->jcc_clk.name); return ENXIO; } return clk_set_rate(clk_parent, rate); } if (jcc->jcc_ops->jcco_setrate) return jcc->jcc_ops->jcco_setrate(sc, jcc, rate); return ENXIO; } static u_int jh71x0_clkc_get_rate(void *priv, struct clk *clk) { struct jh71x0_clkc_softc * const sc = priv; struct jh71x0_clkc_clk * const jcc = container_of(clk, struct jh71x0_clkc_clk, jcc_clk); if (jcc->jcc_ops->jcco_getrate) return jcc->jcc_ops->jcco_getrate(sc, jcc); struct clk * const clk_parent = clk_get_parent(clk); if (clk_parent == NULL) { aprint_debug("%s: no parent for %s\n", __func__, jcc->jcc_clk.name); return 0; } return clk_get_rate(clk_parent); } static int jh71x0_clkc_enable(void *priv, struct clk *clk) { struct jh71x0_clkc_softc * const sc = priv; struct jh71x0_clkc_clk * const jcc = container_of(clk, struct jh71x0_clkc_clk, jcc_clk); struct clk * const clk_parent = clk_get_parent(clk); if (clk_parent != NULL) { int error = clk_enable(clk_parent); if (error != 0) return error; } switch (jcc->jcc_type) { case JH71X0CLK_GATE: jh71x0_clkc_update(sc, jcc, JH71X0_CLK_ENABLE, 0); break; case JH71X0CLK_DIV: { struct jh71x0_clkc_div * const jcc_div = &jcc->jcc_div; if (jcc_div->jcd_flags & JH71X0CLKC_DIV_GATE) { jh71x0_clkc_update(sc, jcc, JH71X0_CLK_ENABLE, 0); break; } break; } case JH71X0CLK_FIXED_FACTOR: case JH71X0CLK_MUX: case JH71X0CLK_INV: break; default: printf("%s: type %d\n", __func__, jcc->jcc_type); return ENXIO; } return 0; } static int jh71x0_clkc_disable(void *priv, struct clk *clk) { struct jh71x0_clkc_softc * const sc = priv; struct jh71x0_clkc_clk * const jcc = container_of(clk, struct jh71x0_clkc_clk, jcc_clk); switch (jcc->jcc_type) { case JH71X0CLK_GATE: jh71x0_clkc_update(sc, jcc, JH71X0_CLK_ENABLE, 0); return 0; default: return ENXIO; } } static struct jh71x0_clkc_clk * jh71x0_clkc_clock_find(struct jh71x0_clkc_softc *sc, const char *name) { for (size_t id = 0; id < sc->sc_nclks; id++) { struct jh71x0_clkc_clk * const jcc = &sc->sc_clk[id]; if (jcc->jcc_clk.name == NULL) continue; if (strcmp(jcc->jcc_clk.name, name) == 0) return jcc; } return NULL; } static int jh71x0_clkc_set_parent(void *priv, struct clk *clk, struct clk *clk_parent) { struct jh71x0_clkc_softc * const sc = priv; struct jh71x0_clkc_clk * const jcc = container_of(clk, struct jh71x0_clkc_clk, jcc_clk); if (jcc->jcc_ops->jcco_setparent == NULL) return EINVAL; return jcc->jcc_ops->jcco_setparent(sc, jcc, clk_parent->name); } static struct clk * jh71x0_clkc_get_parent(void *priv, struct clk *clk) { struct jh71x0_clkc_softc * const sc = priv; struct jh71x0_clkc_clk * const jcc = container_of(clk, struct jh71x0_clkc_clk, jcc_clk); if (jcc->jcc_ops->jcco_getparent == NULL) return NULL; const char *parent = jcc->jcc_ops->jcco_getparent(sc, jcc); if (parent == NULL) return NULL; struct jh71x0_clkc_clk *jcc_parent = jh71x0_clkc_clock_find(sc, parent); if (jcc_parent != NULL) return &jcc_parent->jcc_clk; /* No parent in this domain, try FDT */ return fdtbus_clock_get(sc->sc_phandle, parent); } const struct clk_funcs jh71x0_clkc_funcs = { .get = jh71x0_clkc_get, .put = jh71x0_clkc_put, .set_rate = jh71x0_clkc_set_rate, .get_rate = jh71x0_clkc_get_rate, .enable = jh71x0_clkc_enable, .disable = jh71x0_clkc_disable, .set_parent = jh71x0_clkc_set_parent, .get_parent = jh71x0_clkc_get_parent, };