/* $OpenBSD: sxiccmu.c,v 1.29 2021/10/24 17:52:27 mpi Exp $ */ /* * Copyright (c) 2007,2009 Dale Rahn * Copyright (c) 2013 Artturi Alm * Copyright (c) 2016,2017 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* R40 */ #define R40_GMAC_CLK_REG 0x0164 #ifdef DEBUG_CCMU #define DPRINTF(x) do { printf x; } while (0) #else #define DPRINTF(x) #endif struct sxiccmu_ccu_bit { uint16_t reg; uint8_t bit; uint8_t parent; }; #include "sxiccmu_clocks.h" struct sxiccmu_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; int sc_node; struct sxiccmu_ccu_bit *sc_gates; int sc_ngates; struct clock_device sc_cd; struct sxiccmu_ccu_bit *sc_resets; int sc_nresets; struct reset_device sc_rd; uint32_t (*sc_get_frequency)(struct sxiccmu_softc *, uint32_t); int (*sc_set_frequency)(struct sxiccmu_softc *, uint32_t, uint32_t); }; int sxiccmu_match(struct device *, void *, void *); void sxiccmu_attach(struct device *, struct device *, void *); const struct cfattach sxiccmu_ca = { sizeof (struct sxiccmu_softc), sxiccmu_match, sxiccmu_attach }; struct cfdriver sxiccmu_cd = { NULL, "sxiccmu", DV_DULL }; void sxiccmu_attach_clock(struct sxiccmu_softc *, int, int); uint32_t sxiccmu_ccu_get_frequency(void *, uint32_t *); int sxiccmu_ccu_set_frequency(void *, uint32_t *, uint32_t); void sxiccmu_ccu_enable(void *, uint32_t *, int); void sxiccmu_ccu_reset(void *, uint32_t *, int); uint32_t sxiccmu_a10_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_a10_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_a23_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_a23_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_a64_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_a64_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_a80_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_a80_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_h3_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_h3_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_h3_r_get_frequency(struct sxiccmu_softc *, uint32_t); uint32_t sxiccmu_h6_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_h6_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_h6_r_get_frequency(struct sxiccmu_softc *, uint32_t); uint32_t sxiccmu_r40_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_r40_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_v3s_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_v3s_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); uint32_t sxiccmu_nop_get_frequency(struct sxiccmu_softc *, uint32_t); int sxiccmu_nop_set_frequency(struct sxiccmu_softc *, uint32_t, uint32_t); int sxiccmu_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; int node = faa->fa_node; if (node == OF_finddevice("/clocks")) { node = OF_parent(node); return (OF_is_compatible(node, "allwinner,sun4i-a10") || OF_is_compatible(node, "allwinner,sun5i-a10s") || OF_is_compatible(node, "allwinner,sun5i-r8") || OF_is_compatible(node, "allwinner,sun7i-a20") || OF_is_compatible(node, "allwinner,sun8i-a23") || OF_is_compatible(node, "allwinner,sun8i-a33") || OF_is_compatible(node, "allwinner,sun8i-h3") || OF_is_compatible(node, "allwinner,sun8i-v3s") || OF_is_compatible(node, "allwinner,sun9i-a80") || OF_is_compatible(node, "allwinner,sun50i-a64") || OF_is_compatible(node, "allwinner,sun50i-h5")); } return (OF_is_compatible(node, "allwinner,sun4i-a10-ccu") || OF_is_compatible(node, "allwinner,sun7i-a20-ccu") || OF_is_compatible(node, "allwinner,sun8i-a23-ccu") || OF_is_compatible(node, "allwinner,sun8i-a23-prcm") || OF_is_compatible(node, "allwinner,sun8i-a33-ccu") || OF_is_compatible(node, "allwinner,sun8i-h3-ccu") || OF_is_compatible(node, "allwinner,sun8i-h3-r-ccu") || OF_is_compatible(node, "allwinner,sun8i-r40-ccu") || OF_is_compatible(node, "allwinner,sun8i-v3s-ccu") || OF_is_compatible(node, "allwinner,sun9i-a80-ccu") || OF_is_compatible(node, "allwinner,sun9i-a80-usb-clks") || OF_is_compatible(node, "allwinner,sun9i-a80-mmc-config-clk") || OF_is_compatible(node, "allwinner,sun50i-a64-ccu") || OF_is_compatible(node, "allwinner,sun50i-a64-r-ccu") || OF_is_compatible(node, "allwinner,sun50i-h5-ccu") || OF_is_compatible(node, "allwinner,sun50i-h6-ccu") || OF_is_compatible(node, "allwinner,sun50i-h6-r-ccu")); } void sxiccmu_attach(struct device *parent, struct device *self, void *aux) { struct sxiccmu_softc *sc = (struct sxiccmu_softc *)self; struct fdt_attach_args *faa = aux; int node = faa->fa_node; sc->sc_node = faa->fa_node; sc->sc_iot = faa->fa_iot; if (faa->fa_nreg > 0 && bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh)) panic("%s: bus_space_map failed!", __func__); /* On the R40, the GMAC needs to poke at one of our registers. */ if (OF_is_compatible(node, "allwinner,sun8i-r40-ccu")) { bus_space_handle_t ioh; bus_space_subregion(sc->sc_iot, sc->sc_ioh, R40_GMAC_CLK_REG, 4, &ioh); regmap_register(faa->fa_node, sc->sc_iot, ioh, 4); } printf("\n"); if (OF_is_compatible(node, "allwinner,sun4i-a10-ccu") || OF_is_compatible(node, "allwinner,sun7i-a20-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun4i_a10_gates; sc->sc_ngates = nitems(sun4i_a10_gates); sc->sc_resets = sun4i_a10_resets; sc->sc_nresets = nitems(sun4i_a10_resets); sc->sc_get_frequency = sxiccmu_a10_get_frequency; sc->sc_set_frequency = sxiccmu_a10_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun8i-a23-ccu") || OF_is_compatible(node, "allwinner,sun8i-a33-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun8i_a23_gates; sc->sc_ngates = nitems(sun8i_a23_gates); sc->sc_resets = sun8i_a23_resets; sc->sc_nresets = nitems(sun8i_a23_resets); sc->sc_get_frequency = sxiccmu_a23_get_frequency; sc->sc_set_frequency = sxiccmu_a23_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun8i-h3-ccu") || OF_is_compatible(node, "allwinner,sun50i-h5-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun8i_h3_gates; sc->sc_ngates = nitems(sun8i_h3_gates); sc->sc_resets = sun8i_h3_resets; sc->sc_nresets = nitems(sun8i_h3_resets); sc->sc_get_frequency = sxiccmu_h3_get_frequency; sc->sc_set_frequency = sxiccmu_h3_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun8i-h3-r-ccu") || OF_is_compatible(node, "allwinner,sun50i-a64-r-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun8i_h3_r_gates; sc->sc_ngates = nitems(sun8i_h3_r_gates); sc->sc_resets = sun8i_h3_r_resets; sc->sc_nresets = nitems(sun8i_h3_r_resets); sc->sc_get_frequency = sxiccmu_h3_r_get_frequency; sc->sc_set_frequency = sxiccmu_nop_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun8i-r40-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun8i_r40_gates; sc->sc_ngates = nitems(sun8i_r40_gates); sc->sc_resets = sun8i_r40_resets; sc->sc_nresets = nitems(sun8i_r40_resets); sc->sc_get_frequency = sxiccmu_r40_get_frequency; sc->sc_set_frequency = sxiccmu_r40_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun8i-v3s-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun8i_v3s_gates; sc->sc_ngates = nitems(sun8i_v3s_gates); sc->sc_resets = sun8i_v3s_resets; sc->sc_nresets = nitems(sun8i_v3s_resets); sc->sc_get_frequency = sxiccmu_v3s_get_frequency; sc->sc_set_frequency = sxiccmu_v3s_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun9i-a80-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun9i_a80_gates; sc->sc_ngates = nitems(sun9i_a80_gates); sc->sc_resets = sun9i_a80_resets; sc->sc_nresets = nitems(sun9i_a80_resets); sc->sc_get_frequency = sxiccmu_a80_get_frequency; sc->sc_set_frequency = sxiccmu_a80_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun9i-a80-usb-clks")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun9i_a80_usb_gates; sc->sc_ngates = nitems(sun9i_a80_usb_gates); sc->sc_resets = sun9i_a80_usb_resets; sc->sc_nresets = nitems(sun9i_a80_usb_resets); sc->sc_get_frequency = sxiccmu_nop_get_frequency; sc->sc_set_frequency = sxiccmu_nop_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun9i-a80-mmc-config-clk")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun9i_a80_mmc_gates; sc->sc_ngates = nitems(sun9i_a80_mmc_gates); sc->sc_resets = sun9i_a80_mmc_resets; sc->sc_nresets = nitems(sun9i_a80_mmc_resets); sc->sc_get_frequency = sxiccmu_nop_get_frequency; sc->sc_set_frequency = sxiccmu_nop_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun50i-a64-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun50i_a64_gates; sc->sc_ngates = nitems(sun50i_a64_gates); sc->sc_resets = sun50i_a64_resets; sc->sc_nresets = nitems(sun50i_a64_resets); sc->sc_get_frequency = sxiccmu_a64_get_frequency; sc->sc_set_frequency = sxiccmu_a64_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun50i-h6-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun50i_h6_gates; sc->sc_ngates = nitems(sun50i_h6_gates); sc->sc_resets = sun50i_h6_resets; sc->sc_nresets = nitems(sun50i_h6_resets); sc->sc_get_frequency = sxiccmu_h6_get_frequency; sc->sc_set_frequency = sxiccmu_h6_set_frequency; } else if (OF_is_compatible(node, "allwinner,sun50i-h6-r-ccu")) { KASSERT(faa->fa_nreg > 0); sc->sc_gates = sun50i_h6_r_gates; sc->sc_ngates = nitems(sun50i_h6_r_gates); sc->sc_resets = sun50i_h6_r_resets; sc->sc_nresets = nitems(sun50i_h6_r_resets); sc->sc_get_frequency = sxiccmu_h6_r_get_frequency; sc->sc_set_frequency = sxiccmu_nop_set_frequency; } else { for (node = OF_child(node); node; node = OF_peer(node)) sxiccmu_attach_clock(sc, node, faa->fa_nreg); } if (sc->sc_gates) { sc->sc_cd.cd_node = sc->sc_node; sc->sc_cd.cd_cookie = sc; sc->sc_cd.cd_get_frequency = sxiccmu_ccu_get_frequency; sc->sc_cd.cd_set_frequency = sxiccmu_ccu_set_frequency; sc->sc_cd.cd_enable = sxiccmu_ccu_enable; clock_register(&sc->sc_cd); } if (sc->sc_resets) { sc->sc_rd.rd_node = sc->sc_node; sc->sc_rd.rd_cookie = sc; sc->sc_rd.rd_reset = sxiccmu_ccu_reset; reset_register(&sc->sc_rd); } } /* * Classic device trees for the Allwinner SoCs have basically a clock * node per register of the clock control unit. Attaching a separate * driver to each of them would be crazy, so we handle them here. */ struct sxiccmu_clock { int sc_node; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; struct clock_device sc_cd; struct reset_device sc_rd; }; struct sxiccmu_device { const char *compat; uint32_t (*get_frequency)(void *, uint32_t *); int (*set_frequency)(void *, uint32_t *, uint32_t); void (*enable)(void *, uint32_t *, int); void (*reset)(void *, uint32_t *, int); bus_size_t offset; }; uint32_t sxiccmu_gen_get_frequency(void *, uint32_t *); uint32_t sxiccmu_osc_get_frequency(void *, uint32_t *); uint32_t sxiccmu_pll6_get_frequency(void *, uint32_t *); void sxiccmu_pll6_enable(void *, uint32_t *, int); uint32_t sxiccmu_apb1_get_frequency(void *, uint32_t *); uint32_t sxiccmu_cpus_get_frequency(void *, uint32_t *); uint32_t sxiccmu_apbs_get_frequency(void *, uint32_t *); int sxiccmu_gmac_set_frequency(void *, uint32_t *, uint32_t); int sxiccmu_mmc_set_frequency(void *, uint32_t *, uint32_t); void sxiccmu_mmc_enable(void *, uint32_t *, int); void sxiccmu_gate_enable(void *, uint32_t *, int); void sxiccmu_reset(void *, uint32_t *, int); struct sxiccmu_device sxiccmu_devices[] = { { .compat = "allwinner,sun4i-a10-osc-clk", .get_frequency = sxiccmu_osc_get_frequency, }, { .compat = "allwinner,sun4i-a10-pll6-clk", .get_frequency = sxiccmu_pll6_get_frequency, .enable = sxiccmu_pll6_enable }, { .compat = "allwinner,sun4i-a10-apb1-clk", .get_frequency = sxiccmu_apb1_get_frequency, }, { .compat = "allwinner,sun4i-a10-ahb-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun4i-a10-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun4i-a10-apb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun4i-a10-mmc-clk", .set_frequency = sxiccmu_mmc_set_frequency, .enable = sxiccmu_mmc_enable }, { .compat = "allwinner,sun4i-a10-usb-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable, .reset = sxiccmu_reset }, { .compat = "allwinner,sun5i-a10s-ahb-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun5i-a10s-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun5i-a10s-apb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun5i-a13-ahb-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun5i-a13-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun5i-a13-apb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun5i-a13-usb-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable, .reset = sxiccmu_reset }, { .compat = "allwinner,sun6i-a31-ahb1-reset", .reset = sxiccmu_reset }, { .compat = "allwinner,sun6i-a31-clock-reset", .reset = sxiccmu_reset, .offset = 0x00b0 }, { .compat = "allwinner,sun7i-a20-ahb-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun7i-a20-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun7i-a20-apb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun7i-a20-gmac-clk", .set_frequency = sxiccmu_gmac_set_frequency }, { .compat = "allwinner,sun8i-a23-apb0-clk", .get_frequency = sxiccmu_apbs_get_frequency, .offset = 0x000c }, { .compat = "allwinner,sun8i-a23-ahb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun8i-a23-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable, .offset = 0x0028 }, { .compat = "allwinner,sun8i-a23-apb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun8i-a23-apb2-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun8i-a23-usb-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable, .reset = sxiccmu_reset }, { .compat = "allwinner,sun8i-h3-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-apb1-clk", .get_frequency = sxiccmu_apb1_get_frequency, }, { .compat = "allwinner,sun9i-a80-ahb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-ahb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-ahb2-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-apb0-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-apb1-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-apbs-gates-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable }, { .compat = "allwinner,sun9i-a80-cpus-clk", .get_frequency = sxiccmu_cpus_get_frequency }, { .compat = "allwinner,sun9i-a80-mmc-clk", .set_frequency = sxiccmu_mmc_set_frequency, .enable = sxiccmu_mmc_enable }, { .compat = "allwinner,sun9i-a80-usb-mod-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable, .reset = sxiccmu_reset }, { .compat = "allwinner,sun9i-a80-usb-phy-clk", .get_frequency = sxiccmu_gen_get_frequency, .enable = sxiccmu_gate_enable, .reset = sxiccmu_reset }, }; void sxiccmu_attach_clock(struct sxiccmu_softc *sc, int node, int nreg) { struct sxiccmu_clock *clock; uint32_t reg[2]; int i, error = ENODEV; for (i = 0; i < nitems(sxiccmu_devices); i++) if (OF_is_compatible(node, sxiccmu_devices[i].compat)) break; if (i == nitems(sxiccmu_devices)) return; clock = malloc(sizeof(*clock), M_DEVBUF, M_WAITOK); clock->sc_node = node; clock->sc_iot = sc->sc_iot; if (OF_getpropintarray(node, "reg", reg, sizeof(reg)) == sizeof(reg)) { error = bus_space_map(clock->sc_iot, reg[0], reg[1], 0, &clock->sc_ioh); } else if (nreg > 0) { error = bus_space_subregion(clock->sc_iot, sc->sc_ioh, sxiccmu_devices[i].offset, 4, &clock->sc_ioh); } if (error) { printf("%s: can't map registers", sc->sc_dev.dv_xname); free(clock, M_DEVBUF, sizeof(*clock)); return; } clock->sc_cd.cd_node = node; clock->sc_cd.cd_cookie = clock; clock->sc_cd.cd_get_frequency = sxiccmu_devices[i].get_frequency; clock->sc_cd.cd_set_frequency = sxiccmu_devices[i].set_frequency; clock->sc_cd.cd_enable = sxiccmu_devices[i].enable; clock_register(&clock->sc_cd); if (sxiccmu_devices[i].reset) { clock->sc_rd.rd_node = node; clock->sc_rd.rd_cookie = clock; clock->sc_rd.rd_reset = sxiccmu_devices[i].reset; reset_register(&clock->sc_rd); } } /* * A "generic" function that simply gets the clock frequency from the * parent clock. Useful for clock gating devices that don't scale * their clocks. */ uint32_t sxiccmu_gen_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_clock *sc = cookie; return clock_get_frequency(sc->sc_node, NULL); } uint32_t sxiccmu_osc_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_clock *sc = cookie; return OF_getpropint(sc->sc_node, "clock-frequency", 24000000); } #define CCU_PLL6_ENABLE (1U << 31) #define CCU_PLL6_BYPASS_EN (1U << 30) #define CCU_PLL6_SATA_CLK_EN (1U << 14) #define CCU_PLL6_FACTOR_N(x) (((x) >> 8) & 0x1f) #define CCU_PLL6_FACTOR_N_MASK (0x1f << 8) #define CCU_PLL6_FACTOR_N_SHIFT 8 #define CCU_PLL6_FACTOR_K(x) (((x) >> 4) & 0x3) #define CCU_PLL6_FACTOR_K_MASK (0x3 << 4) #define CCU_PLL6_FACTOR_K_SHIFT 4 #define CCU_PLL6_FACTOR_M(x) (((x) >> 0) & 0x3) #define CCU_PLL6_FACTOR_M_MASK (0x3 << 0) #define CCU_PLL6_FACTOR_M_SHIFT 0 uint32_t sxiccmu_pll6_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_clock *sc = cookie; uint32_t reg, k, m, n, freq; uint32_t idx = cells[0]; /* XXX Assume bypass is disabled. */ reg = SXIREAD4(sc, 0); k = CCU_PLL6_FACTOR_K(reg) + 1; m = CCU_PLL6_FACTOR_M(reg) + 1; n = CCU_PLL6_FACTOR_N(reg); freq = clock_get_frequency_idx(sc->sc_node, 0); switch (idx) { case 0: return (freq * n * k) / m / 6; /* pll6_sata */ case 1: return (freq * n * k) / 2; /* pll6_other */ case 2: return (freq * n * k); /* pll6 */ case 3: return (freq * n * k) / 4; /* pll6_div_4 */ } return 0; } void sxiccmu_pll6_enable(void *cookie, uint32_t *cells, int on) { struct sxiccmu_clock *sc = cookie; uint32_t idx = cells[0]; uint32_t reg; /* * Since this clock has several outputs, we never turn it off. */ reg = SXIREAD4(sc, 0); switch (idx) { case 0: /* pll6_sata */ if (on) reg |= CCU_PLL6_SATA_CLK_EN; else reg &= ~CCU_PLL6_SATA_CLK_EN; /* FALLTHROUGH */ case 1: /* pll6_other */ case 2: /* pll6 */ case 3: /* pll6_div_4 */ if (on) reg |= CCU_PLL6_ENABLE; } SXIWRITE4(sc, 0, reg); } #define CCU_APB1_CLK_RAT_N(x) (((x) >> 16) & 0x3) #define CCU_APB1_CLK_RAT_M(x) (((x) >> 0) & 0x1f) #define CCU_APB1_CLK_SRC_SEL(x) (((x) >> 24) & 0x3) uint32_t sxiccmu_apb1_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_clock *sc = cookie; uint32_t reg, m, n, freq; int idx; reg = SXIREAD4(sc, 0); m = CCU_APB1_CLK_RAT_M(reg); n = CCU_APB1_CLK_RAT_N(reg); idx = CCU_APB1_CLK_SRC_SEL(reg); freq = clock_get_frequency_idx(sc->sc_node, idx); return freq / (1 << n) / (m + 1); } #define CCU_CPUS_CLK_SRC_SEL(x) (((x) >> 16) & 0x3) #define CCU_CPUS_POST_DIV(x) (((x) >> 8) & 0x1f) #define CCU_CPUS_CLK_RATIO(x) (((x) >> 0) & 0x3) uint32_t sxiccmu_cpus_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_clock *sc = cookie; uint32_t reg, post_div, clk_ratio, freq; int idx; reg = SXIREAD4(sc, 0); idx = CCU_CPUS_CLK_SRC_SEL(reg); post_div = (idx == 2 ? CCU_CPUS_POST_DIV(reg): 0); clk_ratio = CCU_CPUS_CLK_RATIO(reg); freq = clock_get_frequency_idx(sc->sc_node, idx); return freq / (clk_ratio + 1) / (post_div + 1); } #define CCU_APBS_CLK_RATIO(x) (((x) >> 0) & 0x3) uint32_t sxiccmu_apbs_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_clock *sc = cookie; uint32_t reg, freq; reg = SXIREAD4(sc, 0); freq = clock_get_frequency(sc->sc_node, NULL); return freq / (CCU_APBS_CLK_RATIO(reg) + 1); } #define CCU_GMAC_CLK_PIT (1 << 2) #define CCU_GMAC_CLK_TCS (3 << 0) #define CCU_GMAC_CLK_TCS_MII 0 #define CCU_GMAC_CLK_TCS_EXT_125 1 #define CCU_GMAC_CLK_TCS_INT_RGMII 2 int sxiccmu_gmac_set_frequency(void *cookie, uint32_t *cells, uint32_t freq) { struct sxiccmu_clock *sc = cookie; switch (freq) { case 25000000: /* MMI, 25 MHz */ SXICMS4(sc, 0, CCU_GMAC_CLK_PIT|CCU_GMAC_CLK_TCS, CCU_GMAC_CLK_TCS_MII); break; case 125000000: /* RGMII, 125 MHz */ SXICMS4(sc, 0, CCU_GMAC_CLK_PIT|CCU_GMAC_CLK_TCS, CCU_GMAC_CLK_PIT|CCU_GMAC_CLK_TCS_INT_RGMII); break; default: return -1; } return 0; } #define CCU_SDx_SCLK_GATING (1U << 31) #define CCU_SDx_CLK_SRC_SEL_OSC24M (0 << 24) #define CCU_SDx_CLK_SRC_SEL_PLL6 (1 << 24) #define CCU_SDx_CLK_SRC_SEL_PLL5 (2 << 24) #define CCU_SDx_CLK_SRC_SEL_MASK (3 << 24) #define CCU_SDx_CLK_DIV_RATIO_N_MASK (3 << 16) #define CCU_SDx_CLK_DIV_RATIO_N_SHIFT 16 #define CCU_SDx_CLK_DIV_RATIO_M_MASK (7 << 0) #define CCU_SDx_CLK_DIV_RATIO_M_SHIFT 0 int sxiccmu_mmc_do_set_frequency(struct sxiccmu_clock *sc, uint32_t freq, uint32_t parent_freq) { uint32_t reg, m, n; uint32_t clk_src; switch (freq) { case 400000: n = 2, m = 15; clk_src = CCU_SDx_CLK_SRC_SEL_OSC24M; break; case 20000000: case 25000000: case 26000000: case 50000000: case 52000000: n = 0, m = 0; clk_src = CCU_SDx_CLK_SRC_SEL_PLL6; while ((parent_freq / (1 << n) / 16) > freq) n++; while ((parent_freq / (1 << n) / (m + 1)) > freq) m++; break; default: return -1; } reg = SXIREAD4(sc, 0); reg &= ~CCU_SDx_CLK_SRC_SEL_MASK; reg |= clk_src; reg &= ~CCU_SDx_CLK_DIV_RATIO_N_MASK; reg |= n << CCU_SDx_CLK_DIV_RATIO_N_SHIFT; reg &= ~CCU_SDx_CLK_DIV_RATIO_M_MASK; reg |= m << CCU_SDx_CLK_DIV_RATIO_M_SHIFT; SXIWRITE4(sc, 0, reg); return 0; } int sxiccmu_mmc_set_frequency(void *cookie, uint32_t *cells, uint32_t freq) { struct sxiccmu_clock *sc = cookie; uint32_t parent_freq; if (cells[0] != 0) return -1; parent_freq = clock_get_frequency_idx(sc->sc_node, 1); return sxiccmu_mmc_do_set_frequency(sc, freq, parent_freq); } void sxiccmu_mmc_enable(void *cookie, uint32_t *cells, int on) { struct sxiccmu_clock *sc = cookie; if (cells[0] != 0) return; if (on) SXISET4(sc, 0, CCU_SDx_SCLK_GATING); else SXICLR4(sc, 0, CCU_SDx_SCLK_GATING); } void sxiccmu_gate_enable(void *cookie, uint32_t *cells, int on) { struct sxiccmu_clock *sc = cookie; int reg = cells[0] / 32; int bit = cells[0] % 32; if (on) { clock_enable(sc->sc_node, NULL); SXISET4(sc, reg * 4, (1U << bit)); } else { SXICLR4(sc, reg * 4, (1U << bit)); clock_disable(sc->sc_node, NULL); } } void sxiccmu_reset(void *cookie, uint32_t *cells, int assert) { struct sxiccmu_clock *sc = cookie; int reg = cells[0] / 32; int bit = cells[0] % 32; if (assert) SXICLR4(sc, reg * 4, (1U << bit)); else SXISET4(sc, reg * 4, (1U << bit)); } /* * Newer device trees, such as those for the Allwinner H3/A64 have * most of the clock nodes replaced with a single clock control unit * node. */ uint32_t sxiccmu_ccu_get_frequency(void *cookie, uint32_t *cells) { struct sxiccmu_softc *sc = cookie; uint32_t idx = cells[0]; uint32_t parent; if (idx < sc->sc_ngates && sc->sc_gates[idx].parent) { parent = sc->sc_gates[idx].parent; return sxiccmu_ccu_get_frequency(sc, &parent); } return sc->sc_get_frequency(sc, idx); } /* Allwinner A10/A20 */ #define A10_PLL1_CFG_REG 0x0000 #define A10_PLL1_OUT_EXT_DIVP_MASK (0x3 << 16) #define A10_PLL1_OUT_EXT_DIVP_SHIFT 16 #define A10_PLL1_OUT_EXT_DIVP(x) (((x) >> 16) & 0x3) #define A10_PLL1_FACTOR_N(x) (((x) >> 8) & 0x1f) #define A10_PLL1_FACTOR_N_MASK (0x1f << 8) #define A10_PLL1_FACTOR_N_SHIFT 8 #define A10_PLL1_FACTOR_K(x) (((x) >> 4) & 0x3) #define A10_PLL1_FACTOR_K_MASK (0x3 << 4) #define A10_PLL1_FACTOR_K_SHIFT 4 #define A10_PLL1_FACTOR_M(x) (((x) >> 0) & 0x3) #define A10_PLL1_FACTOR_M_MASK (0x3 << 0) #define A10_PLL1_FACTOR_M_SHIFT 0 #define A10_CPU_AHB_APB0_CFG_REG 0x0054 #define A10_CPU_CLK_SRC_SEL (0x3 << 16) #define A10_CPU_CLK_SRC_SEL_LOSC (0x0 << 16) #define A10_CPU_CLK_SRC_SEL_OSC24M (0x1 << 16) #define A10_CPU_CLK_SRC_SEL_PLL1 (0x2 << 16) #define A10_CPU_CLK_SRC_SEL_200MHZ (0x3 << 16) #define A10_AHB_CLK_DIV_RATIO(x) (((x) >> 8) & 0x3) #define A10_AXI_CLK_DIV_RATIO(x) (((x) >> 0) & 0x3) uint32_t sxiccmu_a10_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; uint32_t k, m, n, p; switch (idx) { case A10_CLK_LOSC: return clock_get_frequency(sc->sc_node, "losc"); case A10_CLK_HOSC: return clock_get_frequency(sc->sc_node, "hosc"); case A10_CLK_PLL_CORE: reg = SXIREAD4(sc, A10_PLL1_CFG_REG); k = A10_PLL1_FACTOR_K(reg) + 1; m = A10_PLL1_FACTOR_M(reg) + 1; n = A10_PLL1_FACTOR_N(reg); p = 1 << A10_PLL1_OUT_EXT_DIVP(reg); return (24000000 * n * k) / (m * p); case A10_CLK_PLL_PERIPH_BASE: /* Not hardcoded, but recommended. */ return 600000000; case A10_CLK_PLL_PERIPH: return sxiccmu_a10_get_frequency(sc, A10_CLK_PLL_PERIPH_BASE) * 2; case A10_CLK_CPU: reg = SXIREAD4(sc, A10_CPU_AHB_APB0_CFG_REG); switch (reg & A10_CPU_CLK_SRC_SEL) { case A10_CPU_CLK_SRC_SEL_LOSC: parent = A10_CLK_LOSC; break; case A10_CPU_CLK_SRC_SEL_OSC24M: parent = A10_CLK_HOSC; break; case A10_CPU_CLK_SRC_SEL_PLL1: parent = A10_CLK_PLL_CORE; break; case A10_CPU_CLK_SRC_SEL_200MHZ: return 200000000; } return sxiccmu_ccu_get_frequency(sc, &parent); case A10_CLK_AXI: reg = SXIREAD4(sc, A10_CPU_AHB_APB0_CFG_REG); div = 1 << A10_AXI_CLK_DIV_RATIO(reg); parent = A10_CLK_CPU; return sxiccmu_ccu_get_frequency(sc, &parent) / div; case A10_CLK_AHB: reg = SXIREAD4(sc, A10_CPU_AHB_APB0_CFG_REG); div = 1 << A10_AHB_CLK_DIV_RATIO(reg); parent = A10_CLK_AXI; return sxiccmu_ccu_get_frequency(sc, &parent) / div; case A10_CLK_APB1: /* XXX Controlled by a MUX. */ return 24000000; } printf("%s: 0x%08x\n", __func__, idx); return 0; } /* Allwinner A23/A64/H3/H5/R40 */ #define CCU_AHB1_APB1_CFG_REG 0x0054 #define CCU_AHB1_CLK_SRC_SEL (3 << 12) #define CCU_AHB1_CLK_SRC_SEL_LOSC (0 << 12) #define CCU_AHB1_CLK_SRC_SEL_OSC24M (1 << 12) #define CCU_AHB1_CLK_SRC_SEL_AXI (2 << 12) #define CCU_AHB1_CLK_SRC_SEL_PERIPH0 (3 << 12) #define CCU_AHB1_PRE_DIV(x) ((((x) >> 6) & 3) + 1) #define CCU_AHB1_CLK_DIV_RATIO(x) (1 << (((x) >> 4) & 3)) #define CCU_AHB2_CFG_REG 0x005c #define CCU_AHB2_CLK_CFG (3 << 0) uint32_t sxiccmu_a23_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; switch (idx) { case A23_CLK_LOSC: return clock_get_frequency(sc->sc_node, "losc"); case A23_CLK_HOSC: return clock_get_frequency(sc->sc_node, "hosc"); case A23_CLK_PLL_PERIPH: /* Not hardcoded, but recommended. */ return 600000000; case A23_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; case A23_CLK_AHB1: reg = SXIREAD4(sc, CCU_AHB1_APB1_CFG_REG); div = CCU_AHB1_CLK_DIV_RATIO(reg); switch (reg & CCU_AHB1_CLK_SRC_SEL) { case CCU_AHB1_CLK_SRC_SEL_LOSC: parent = A23_CLK_LOSC; break; case CCU_AHB1_CLK_SRC_SEL_OSC24M: parent = A23_CLK_HOSC; break; case CCU_AHB1_CLK_SRC_SEL_AXI: parent = A23_CLK_AXI; break; case CCU_AHB1_CLK_SRC_SEL_PERIPH0: parent = A23_CLK_PLL_PERIPH; div *= CCU_AHB1_PRE_DIV(reg); break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; } printf("%s: 0x%08x\n", __func__, idx); return 0; } #define A64_PLL_CPUX_CTRL_REG 0x0000 #define A64_PLL_CPUX_LOCK (1 << 28) #define A64_PLL_CPUX_OUT_EXT_DIVP(x) (((x) >> 16) & 0x3) #define A64_PLL_CPUX_OUT_EXT_DIVP_MASK (0x3 << 16) #define A64_PLL_CPUX_FACTOR_N(x) (((x) >> 8) & 0x1f) #define A64_PLL_CPUX_FACTOR_N_MASK (0x1f << 8) #define A64_PLL_CPUX_FACTOR_N_SHIFT 8 #define A64_PLL_CPUX_FACTOR_K(x) (((x) >> 4) & 0x3) #define A64_PLL_CPUX_FACTOR_K_MASK (0x3 << 4) #define A64_PLL_CPUX_FACTOR_K_SHIFT 4 #define A64_PLL_CPUX_FACTOR_M(x) (((x) >> 0) & 0x3) #define A64_PLL_CPUX_FACTOR_M_MASK (0x3 << 0) #define A64_CPUX_AXI_CFG_REG 0x0050 #define A64_CPUX_CLK_SRC_SEL (0x3 << 16) #define A64_CPUX_CLK_SRC_SEL_LOSC (0x0 << 16) #define A64_CPUX_CLK_SRC_SEL_OSC24M (0x1 << 16) #define A64_CPUX_CLK_SRC_SEL_PLL_CPUX (0x2 << 16) uint32_t sxiccmu_a64_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; uint32_t k, m, n, p; switch (idx) { case A64_CLK_PLL_CPUX: reg = SXIREAD4(sc, A64_PLL_CPUX_CTRL_REG); k = A64_PLL_CPUX_FACTOR_K(reg) + 1; m = A64_PLL_CPUX_FACTOR_M(reg) + 1; n = A64_PLL_CPUX_FACTOR_N(reg) + 1; p = 1 << A64_PLL_CPUX_OUT_EXT_DIVP(reg); return (24000000 * n * k) / (m * p); case A64_CLK_CPUX: reg = SXIREAD4(sc, A64_CPUX_AXI_CFG_REG); switch (reg & A64_CPUX_CLK_SRC_SEL) { case A64_CPUX_CLK_SRC_SEL_LOSC: parent = A64_CLK_LOSC; break; case A64_CPUX_CLK_SRC_SEL_OSC24M: parent = A64_CLK_HOSC; break; case A64_CPUX_CLK_SRC_SEL_PLL_CPUX: parent = A64_CLK_PLL_CPUX; break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent); case A64_CLK_LOSC: return clock_get_frequency(sc->sc_node, "losc"); case A64_CLK_HOSC: return clock_get_frequency(sc->sc_node, "hosc"); case A64_CLK_PLL_PERIPH0: /* Not hardcoded, but recommended. */ return 600000000; case A64_CLK_PLL_PERIPH0_2X: return sxiccmu_a64_get_frequency(sc, A64_CLK_PLL_PERIPH0) * 2; case A64_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; case A64_CLK_AHB1: reg = SXIREAD4(sc, CCU_AHB1_APB1_CFG_REG); div = CCU_AHB1_CLK_DIV_RATIO(reg); switch (reg & CCU_AHB1_CLK_SRC_SEL) { case CCU_AHB1_CLK_SRC_SEL_LOSC: parent = A64_CLK_LOSC; break; case CCU_AHB1_CLK_SRC_SEL_OSC24M: parent = A64_CLK_HOSC; break; case CCU_AHB1_CLK_SRC_SEL_AXI: parent = A64_CLK_AXI; break; case CCU_AHB1_CLK_SRC_SEL_PERIPH0: parent = A64_CLK_PLL_PERIPH0; div *= CCU_AHB1_PRE_DIV(reg); break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; case A64_CLK_AHB2: reg = SXIREAD4(sc, CCU_AHB2_CFG_REG); switch (reg & CCU_AHB2_CLK_CFG) { case 0: parent = A64_CLK_AHB1; div = 1; break; case 1: parent = A64_CLK_PLL_PERIPH0; div = 2; break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; } printf("%s: 0x%08x\n", __func__, idx); return 0; } #define A80_AHB1_CLK_CFG_REG 0x0064 #define A80_AHB1_SRC_CLK_SELECT (3 << 24) #define A80_AHB1_SRC_CLK_SELECT_GTBUS (0 << 24) #define A80_AHB1_SRC_CLK_SELECT_PERIPH0 (1 << 24) #define A80_AHB1_CLK_DIV_RATIO(x) (1 << ((x) & 0x3)) uint32_t sxiccmu_a80_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; switch (idx) { case A80_CLK_PLL_PERIPH0: /* Not hardcoded, but recommended. */ return 960000000; case A80_CLK_AHB1: reg = SXIREAD4(sc, A80_AHB1_CLK_CFG_REG); div = A80_AHB1_CLK_DIV_RATIO(reg); switch (reg & A80_AHB1_SRC_CLK_SELECT) { case A80_AHB1_SRC_CLK_SELECT_GTBUS: parent = A80_CLK_GTBUS; break; case A80_AHB1_SRC_CLK_SELECT_PERIPH0: parent = A80_CLK_PLL_PERIPH0; break; default: parent = A80_CLK_PLL_PERIPH1; break; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; case A80_CLK_APB1: /* XXX Controlled by a MUX. */ return 24000000; } printf("%s: 0x%08x\n", __func__, idx); return 0; } /* Allwinner H3/H5 */ #define H3_PLL_CPUX_CTRL_REG 0x0000 #define H3_PLL_CPUX_ENABLE (1 << 31) #define H3_PLL_CPUX_LOCK (1 << 28) #define H3_PLL_CPUX_OUT_EXT_DIVP(x) (((x) >> 16) & 0x3) #define H3_PLL_CPUX_OUT_EXT_DIVP_MASK (0x3 << 16) #define H3_PLL_CPUX_OUT_EXT_DIVP_SHIFT 16 #define H3_PLL_CPUX_FACTOR_N(x) (((x) >> 8) & 0x1f) #define H3_PLL_CPUX_FACTOR_N_MASK (0x1f << 8) #define H3_PLL_CPUX_FACTOR_N_SHIFT 8 #define H3_PLL_CPUX_FACTOR_K(x) (((x) >> 4) & 0x3) #define H3_PLL_CPUX_FACTOR_K_MASK (0x3 << 4) #define H3_PLL_CPUX_FACTOR_K_SHIFT 4 #define H3_PLL_CPUX_FACTOR_M(x) (((x) >> 0) & 0x3) #define H3_PLL_CPUX_FACTOR_M_MASK (0x3 << 0) #define H3_PLL_CPUX_FACTOR_M_SHIFT 0 #define H3_CPUX_AXI_CFG_REG 0x0050 #define H3_CPUX_CLK_SRC_SEL (0x3 << 16) #define H3_CPUX_CLK_SRC_SEL_LOSC (0x0 << 16) #define H3_CPUX_CLK_SRC_SEL_OSC24M (0x1 << 16) #define H3_CPUX_CLK_SRC_SEL_PLL_CPUX (0x2 << 16) #define H3_PLL_STABLE_TIME_REG1 0x0204 #define H3_PLL_STABLE_TIME_REG1_TIME(x) (((x) >> 0) & 0xffff) uint32_t sxiccmu_h3_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; uint32_t k, m, n, p; switch (idx) { case H3_CLK_LOSC: return clock_get_frequency(sc->sc_node, "losc"); case H3_CLK_HOSC: return clock_get_frequency(sc->sc_node, "hosc"); case H3_CLK_PLL_CPUX: reg = SXIREAD4(sc, H3_PLL_CPUX_CTRL_REG); k = H3_PLL_CPUX_FACTOR_K(reg) + 1; m = H3_PLL_CPUX_FACTOR_M(reg) + 1; n = H3_PLL_CPUX_FACTOR_N(reg) + 1; p = 1 << H3_PLL_CPUX_OUT_EXT_DIVP(reg); return (24000000 * n * k) / (m * p); case H3_CLK_PLL_PERIPH0: /* Not hardcoded, but recommended. */ return 600000000; case H3_CLK_CPUX: reg = SXIREAD4(sc, H3_CPUX_AXI_CFG_REG); switch (reg & H3_CPUX_CLK_SRC_SEL) { case H3_CPUX_CLK_SRC_SEL_LOSC: parent = H3_CLK_LOSC; break; case H3_CPUX_CLK_SRC_SEL_OSC24M: parent = H3_CLK_HOSC; break; case H3_CPUX_CLK_SRC_SEL_PLL_CPUX: parent = H3_CLK_PLL_CPUX; break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent); case H3_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; case H3_CLK_AHB1: reg = SXIREAD4(sc, CCU_AHB1_APB1_CFG_REG); div = CCU_AHB1_CLK_DIV_RATIO(reg); switch (reg & CCU_AHB1_CLK_SRC_SEL) { case CCU_AHB1_CLK_SRC_SEL_LOSC: parent = H3_CLK_LOSC; break; case CCU_AHB1_CLK_SRC_SEL_OSC24M: parent = H3_CLK_HOSC; break; case CCU_AHB1_CLK_SRC_SEL_AXI: parent = H3_CLK_AXI; break; case CCU_AHB1_CLK_SRC_SEL_PERIPH0: parent = H3_CLK_PLL_PERIPH0; div *= CCU_AHB1_PRE_DIV(reg); break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; case H3_CLK_AHB2: reg = SXIREAD4(sc, CCU_AHB2_CFG_REG); switch (reg & CCU_AHB2_CLK_CFG) { case 0: parent = H3_CLK_AHB1; div = 1; break; case 1: parent = H3_CLK_PLL_PERIPH0; div = 2; break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; } printf("%s: 0x%08x\n", __func__, idx); return 0; } #define H3_AHB0_CLK_REG 0x0000 #define H3_AHB0_CLK_SRC_SEL (0x3 << 16) #define H3_AHB0_CLK_SRC_SEL_OSC32K (0x0 << 16) #define H3_AHB0_CLK_SRC_SEL_OSC24M (0x1 << 16) #define H3_AHB0_CLK_SRC_SEL_PLL_PERIPH0 (0x2 << 16) #define H3_AHB0_CLK_SRC_SEL_IOSC (0x3 << 16) #define H3_AHB0_CLK_PRE_DIV(x) ((((x) >> 8) & 0x1f) + 1) #define H3_AHB0_CLK_RATIO(x) (1 << (((x) >> 4) & 3)) #define H3_APB0_CFG_REG 0x000c #define H3_APB0_CLK_RATIO(x) (1 << ((x) & 1)) uint32_t sxiccmu_h3_r_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; uint32_t freq; switch (idx) { case H3_R_CLK_AHB0: reg = SXIREAD4(sc, H3_AHB0_CLK_REG); switch (reg & H3_AHB0_CLK_SRC_SEL) { case H3_AHB0_CLK_SRC_SEL_OSC32K: freq = clock_get_frequency(sc->sc_node, "losc"); break; case H3_AHB0_CLK_SRC_SEL_OSC24M: freq = clock_get_frequency(sc->sc_node, "hosc"); break; case H3_AHB0_CLK_SRC_SEL_PLL_PERIPH0: freq = clock_get_frequency(sc->sc_node, "pll-periph"); break; case H3_AHB0_CLK_SRC_SEL_IOSC: freq = clock_get_frequency(sc->sc_node, "iosc"); break; } div = H3_AHB0_CLK_PRE_DIV(reg) * H3_AHB0_CLK_RATIO(reg); return freq / div; case H3_R_CLK_APB0: reg = SXIREAD4(sc, H3_APB0_CFG_REG); div = H3_APB0_CLK_RATIO(reg); parent = H3_R_CLK_AHB0; return sxiccmu_ccu_get_frequency(sc, &parent) / div; } printf("%s: 0x%08x\n", __func__, idx); return 0; } uint32_t sxiccmu_h6_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { switch (idx) { case H6_CLK_PLL_PERIPH0: /* Not hardcoded, but recommended. */ return 600000000; case H6_CLK_PLL_PERIPH0_2X: return sxiccmu_h6_get_frequency(sc, H6_CLK_PLL_PERIPH0) * 2; case H6_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; break; } printf("%s: 0x%08x\n", __func__, idx); return 0; } uint32_t sxiccmu_h6_r_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { switch (idx) { case H6_R_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; break; } printf("%s: 0x%08x\n", __func__, idx); return 0; } uint32_t sxiccmu_r40_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; switch (idx) { case R40_CLK_LOSC: return clock_get_frequency(sc->sc_node, "losc"); case R40_CLK_HOSC: return clock_get_frequency(sc->sc_node, "hosc"); case R40_CLK_PLL_PERIPH0: /* Not hardcoded, but recommended. */ return 600000000; case R40_CLK_PLL_PERIPH0_2X: return sxiccmu_r40_get_frequency(sc, R40_CLK_PLL_PERIPH0) * 2; case R40_CLK_AHB1: reg = SXIREAD4(sc, CCU_AHB1_APB1_CFG_REG); div = CCU_AHB1_CLK_DIV_RATIO(reg); switch (reg & CCU_AHB1_CLK_SRC_SEL) { case CCU_AHB1_CLK_SRC_SEL_LOSC: parent = R40_CLK_LOSC; break; case CCU_AHB1_CLK_SRC_SEL_OSC24M: parent = R40_CLK_HOSC; break; case CCU_AHB1_CLK_SRC_SEL_AXI: parent = R40_CLK_AXI; break; case CCU_AHB1_CLK_SRC_SEL_PERIPH0: parent = R40_CLK_PLL_PERIPH0; div *= CCU_AHB1_PRE_DIV(reg); break; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; case R40_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; } printf("%s: 0x%08x\n", __func__, idx); return 0; } uint32_t sxiccmu_v3s_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { uint32_t parent; uint32_t reg, div; switch (idx) { case V3S_CLK_LOSC: return clock_get_frequency(sc->sc_node, "losc"); case V3S_CLK_HOSC: return clock_get_frequency(sc->sc_node, "hosc"); case V3S_CLK_PLL_PERIPH0: /* Not hardcoded, but recommended. */ return 600000000; case V3S_CLK_APB2: /* XXX Controlled by a MUX. */ return 24000000; case V3S_CLK_AHB1: reg = SXIREAD4(sc, CCU_AHB1_APB1_CFG_REG); div = CCU_AHB1_CLK_DIV_RATIO(reg); switch (reg & CCU_AHB1_CLK_SRC_SEL) { case CCU_AHB1_CLK_SRC_SEL_LOSC: parent = V3S_CLK_LOSC; break; case CCU_AHB1_CLK_SRC_SEL_OSC24M: parent = V3S_CLK_HOSC; break; case CCU_AHB1_CLK_SRC_SEL_AXI: parent = V3S_CLK_AXI; break; case CCU_AHB1_CLK_SRC_SEL_PERIPH0: parent = V3S_CLK_PLL_PERIPH0; div *= CCU_AHB1_PRE_DIV(reg); break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; case V3S_CLK_AHB2: reg = SXIREAD4(sc, CCU_AHB2_CFG_REG); switch (reg & CCU_AHB2_CLK_CFG) { case 0: parent = V3S_CLK_AHB1; div = 1; break; case 1: parent = V3S_CLK_PLL_PERIPH0; div = 2; break; default: return 0; } return sxiccmu_ccu_get_frequency(sc, &parent) / div; } printf("%s: 0x%08x\n", __func__, idx); return 0; } uint32_t sxiccmu_nop_get_frequency(struct sxiccmu_softc *sc, uint32_t idx) { printf("%s: 0x%08x\n", __func__, idx); return 0; } int sxiccmu_ccu_set_frequency(void *cookie, uint32_t *cells, uint32_t freq) { struct sxiccmu_softc *sc = cookie; uint32_t idx = cells[0]; return sc->sc_set_frequency(sc, idx, freq); } int sxiccmu_a10_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; uint32_t reg; int k, n; int error; switch (idx) { case A10_CLK_PLL_CORE: k = 1; n = 32; while (k <= 4 && (24000000 * n * k) < freq) k++; while (n >= 1 && (24000000 * n * k) > freq) n--; reg = SXIREAD4(sc, A10_PLL1_CFG_REG); reg &= ~A10_PLL1_OUT_EXT_DIVP_MASK; reg &= ~A10_PLL1_FACTOR_N_MASK; reg &= ~A10_PLL1_FACTOR_K_MASK; reg &= ~A10_PLL1_FACTOR_M_MASK; reg |= (n << A10_PLL1_FACTOR_N_SHIFT); reg |= ((k - 1) << A10_PLL1_FACTOR_K_SHIFT); SXIWRITE4(sc, A10_PLL1_CFG_REG, reg); /* No need to wait PLL to lock? */ return 0; case A10_CLK_CPU: /* Switch to 24 MHz clock. */ reg = SXIREAD4(sc, A10_CPU_AHB_APB0_CFG_REG); reg &= ~A10_CPU_CLK_SRC_SEL; reg |= A10_CPU_CLK_SRC_SEL_OSC24M; SXIWRITE4(sc, A10_CPU_AHB_APB0_CFG_REG, reg); error = sxiccmu_a10_set_frequency(sc, A10_CLK_PLL_CORE, freq); /* Switch back to PLL. */ reg = SXIREAD4(sc, A10_CPU_AHB_APB0_CFG_REG); reg &= ~A10_CPU_CLK_SRC_SEL; reg |= A10_CPU_CLK_SRC_SEL_PLL1; SXIWRITE4(sc, A10_CPU_AHB_APB0_CFG_REG, reg); return error; case A10_CLK_MMC0: case A10_CLK_MMC1: case A10_CLK_MMC2: case A10_CLK_MMC3: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = A10_CLK_PLL_PERIPH; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_a23_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; switch (idx) { case A23_CLK_MMC0: case A23_CLK_MMC1: case A23_CLK_MMC2: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = A23_CLK_PLL_PERIPH; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_a64_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; uint32_t reg; int k, n; int error; switch (idx) { case A64_CLK_PLL_CPUX: k = 1; n = 32; while (k <= 4 && (24000000 * n * k) < freq) k++; while (n >= 1 && (24000000 * n * k) > freq) n--; reg = SXIREAD4(sc, A64_PLL_CPUX_CTRL_REG); reg &= ~A64_PLL_CPUX_OUT_EXT_DIVP_MASK; reg &= ~A64_PLL_CPUX_FACTOR_N_MASK; reg &= ~A64_PLL_CPUX_FACTOR_K_MASK; reg &= ~A64_PLL_CPUX_FACTOR_M_MASK; reg |= ((n - 1) << A64_PLL_CPUX_FACTOR_N_SHIFT); reg |= ((k - 1) << A64_PLL_CPUX_FACTOR_K_SHIFT); SXIWRITE4(sc, A64_PLL_CPUX_CTRL_REG, reg); /* Wait for PLL to lock. */ while ((SXIREAD4(sc, A64_PLL_CPUX_CTRL_REG) & A64_PLL_CPUX_LOCK) == 0) { delay(200); } return 0; case A64_CLK_CPUX: /* Switch to 24 MHz clock. */ reg = SXIREAD4(sc, A64_CPUX_AXI_CFG_REG); reg &= ~A64_CPUX_CLK_SRC_SEL; reg |= A64_CPUX_CLK_SRC_SEL_OSC24M; SXIWRITE4(sc, A64_CPUX_AXI_CFG_REG, reg); error = sxiccmu_a64_set_frequency(sc, A64_CLK_PLL_CPUX, freq); /* Switch back to PLL. */ reg = SXIREAD4(sc, A64_CPUX_AXI_CFG_REG); reg &= ~A64_CPUX_CLK_SRC_SEL; reg |= A64_CPUX_CLK_SRC_SEL_PLL_CPUX; SXIWRITE4(sc, A64_CPUX_AXI_CFG_REG, reg); return error; case A64_CLK_MMC0: case A64_CLK_MMC1: case A64_CLK_MMC2: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = A64_CLK_PLL_PERIPH0_2X; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_a80_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; switch (idx) { case A80_CLK_MMC0: case A80_CLK_MMC1: case A80_CLK_MMC2: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = A80_CLK_PLL_PERIPH0; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_h3_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; uint32_t reg, lock_time; int k, n; int error; switch (idx) { case H3_CLK_PLL_CPUX: k = 1; n = 32; while (k <= 4 && (24000000 * n * k) < freq) k++; while (n >= 1 && (24000000 * n * k) > freq) n--; /* Gate the PLL first */ reg = SXIREAD4(sc, H3_PLL_CPUX_CTRL_REG); reg &= ~H3_PLL_CPUX_ENABLE; SXIWRITE4(sc, H3_PLL_CPUX_CTRL_REG, reg); /* Set factors and external divider. */ reg &= ~H3_PLL_CPUX_OUT_EXT_DIVP_MASK; reg &= ~H3_PLL_CPUX_FACTOR_N_MASK; reg &= ~H3_PLL_CPUX_FACTOR_K_MASK; reg &= ~H3_PLL_CPUX_FACTOR_M_MASK; reg |= ((n - 1) << H3_PLL_CPUX_FACTOR_N_SHIFT); reg |= ((k - 1) << H3_PLL_CPUX_FACTOR_K_SHIFT); SXIWRITE4(sc, H3_PLL_CPUX_CTRL_REG, reg); /* Ungate the PLL */ reg |= H3_PLL_CPUX_ENABLE; SXIWRITE4(sc, H3_PLL_CPUX_CTRL_REG, reg); /* Wait for PLL to lock. (LOCK flag is unreliable) */ lock_time = SXIREAD4(sc, H3_PLL_STABLE_TIME_REG1); delay(H3_PLL_STABLE_TIME_REG1_TIME(lock_time)); return 0; case H3_CLK_CPUX: /* Switch to 24 MHz clock. */ reg = SXIREAD4(sc, H3_CPUX_AXI_CFG_REG); reg &= ~H3_CPUX_CLK_SRC_SEL; reg |= H3_CPUX_CLK_SRC_SEL_OSC24M; SXIWRITE4(sc, H3_CPUX_AXI_CFG_REG, reg); /* Must wait at least 8 cycles of the current clock. */ delay(1); error = sxiccmu_h3_set_frequency(sc, H3_CLK_PLL_CPUX, freq); /* Switch back to PLL. */ reg = SXIREAD4(sc, H3_CPUX_AXI_CFG_REG); reg &= ~H3_CPUX_CLK_SRC_SEL; reg |= H3_CPUX_CLK_SRC_SEL_PLL_CPUX; SXIWRITE4(sc, H3_CPUX_AXI_CFG_REG, reg); /* Must wait at least 8 cycles of the current clock. */ delay(1); return error; case H3_CLK_MMC0: case H3_CLK_MMC1: case H3_CLK_MMC2: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = H3_CLK_PLL_PERIPH0; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } #define H6_SMHC0_CLK_REG 0x0830 #define H6_SMHC1_CLK_REG 0x0834 #define H6_SMHC2_CLK_REG 0x0838 #define H6_SMHC_CLK_SRC_SEL (0x3 << 24) #define H6_SMHC_CLK_SRC_SEL_OSC24M (0x0 << 24) #define H6_SMHC_CLK_SRC_SEL_PLL_PERIPH0_2X (0x1 << 24) #define H6_SMHC_FACTOR_N_MASK (0x3 << 8) #define H6_SMHC_FACTOR_N_SHIFT 8 #define H6_SMHC_FACTOR_M_MASK (0xf << 0) #define H6_SMHC_FACTOR_M_SHIFT 0 int sxiccmu_h6_mmc_set_frequency(struct sxiccmu_softc *sc, bus_size_t offset, uint32_t freq) { uint32_t parent_freq; uint32_t reg, m, n; uint32_t clk_src; switch (freq) { case 400000: n = 2, m = 15; clk_src = H6_SMHC_CLK_SRC_SEL_OSC24M; break; case 20000000: case 25000000: case 26000000: case 50000000: case 52000000: n = 0, m = 0; clk_src = H6_SMHC_CLK_SRC_SEL_PLL_PERIPH0_2X; parent_freq = sxiccmu_h6_get_frequency(sc, H6_CLK_PLL_PERIPH0_2X); while ((parent_freq / (1 << n) / 16) > freq) n++; while ((parent_freq / (1 << n) / (m + 1)) > freq) m++; break; default: return -1; } reg = SXIREAD4(sc, offset); reg &= ~H6_SMHC_CLK_SRC_SEL; reg |= clk_src; reg &= ~H6_SMHC_FACTOR_N_MASK; reg |= n << H6_SMHC_FACTOR_N_SHIFT; reg &= ~H6_SMHC_FACTOR_M_MASK; reg |= m << H6_SMHC_FACTOR_M_SHIFT; SXIWRITE4(sc, offset, reg); return 0; } int sxiccmu_h6_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { switch (idx) { case H6_CLK_MMC0: return sxiccmu_h6_mmc_set_frequency(sc, H6_SMHC0_CLK_REG, freq); case H6_CLK_MMC1: return sxiccmu_h6_mmc_set_frequency(sc, H6_SMHC1_CLK_REG, freq); case H6_CLK_MMC2: return sxiccmu_h6_mmc_set_frequency(sc, H6_SMHC2_CLK_REG, freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_r40_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; switch (idx) { case R40_CLK_MMC0: case R40_CLK_MMC1: case R40_CLK_MMC2: case R40_CLK_MMC3: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = R40_CLK_PLL_PERIPH0_2X; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_v3s_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { struct sxiccmu_clock clock; uint32_t parent, parent_freq; switch (idx) { case V3S_CLK_MMC0: case V3S_CLK_MMC1: case V3S_CLK_MMC2: clock.sc_iot = sc->sc_iot; bus_space_subregion(sc->sc_iot, sc->sc_ioh, sc->sc_gates[idx].reg, 4, &clock.sc_ioh); parent = V3S_CLK_PLL_PERIPH0; parent_freq = sxiccmu_ccu_get_frequency(sc, &parent); return sxiccmu_mmc_do_set_frequency(&clock, freq, parent_freq); } printf("%s: 0x%08x\n", __func__, idx); return -1; } int sxiccmu_nop_set_frequency(struct sxiccmu_softc *sc, uint32_t idx, uint32_t freq) { printf("%s: 0x%08x\n", __func__, idx); return -1; } void sxiccmu_ccu_enable(void *cookie, uint32_t *cells, int on) { struct sxiccmu_softc *sc = cookie; uint32_t idx = cells[0]; int reg, bit; clock_enable_all(sc->sc_node); if (idx >= sc->sc_ngates || (sc->sc_gates[idx].reg == 0 && sc->sc_gates[idx].bit == 0)) { printf("%s: 0x%08x\n", __func__, cells[0]); return; } /* If the clock can't be gated, simply return. */ if (sc->sc_gates[idx].reg == 0xffff && sc->sc_gates[idx].bit == 0xff) return; reg = sc->sc_gates[idx].reg; bit = sc->sc_gates[idx].bit; if (on) SXISET4(sc, reg, (1U << bit)); else SXICLR4(sc, reg, (1U << bit)); } void sxiccmu_ccu_reset(void *cookie, uint32_t *cells, int assert) { struct sxiccmu_softc *sc = cookie; uint32_t idx = cells[0]; int reg, bit; reset_deassert_all(sc->sc_node); if (idx >= sc->sc_nresets || (sc->sc_resets[idx].reg == 0 && sc->sc_gates[idx].bit == 0)) { printf("%s: 0x%08x\n", __func__, cells[0]); return; } reg = sc->sc_resets[idx].reg; bit = sc->sc_resets[idx].bit; if (assert) SXICLR4(sc, reg, (1U << bit)); else SXISET4(sc, reg, (1U << bit)); }