diff options
Diffstat (limited to 'sys')
| -rw-r--r-- | sys/dev/clk/spacemit/k1_apmu.c | 114 | ||||
| -rw-r--r-- | sys/dev/clk/spacemit/k1_clk.c | 409 | ||||
| -rw-r--r-- | sys/dev/clk/spacemit/k1_clk.h | 146 | ||||
| -rw-r--r-- | sys/dev/clk/spacemit/k1_pll.c | 200 | ||||
| -rw-r--r-- | sys/riscv/conf/std.spacemit | 1 | ||||
| -rw-r--r-- | sys/riscv/spacemit/files.spacemit | 3 |
6 files changed, 873 insertions, 0 deletions
diff --git a/sys/dev/clk/spacemit/k1_apmu.c b/sys/dev/clk/spacemit/k1_apmu.c new file mode 100644 index 000000000000..8869c7e74f47 --- /dev/null +++ b/sys/dev/clk/spacemit/k1_apmu.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2026 Bojan Novković <bnovkov@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Driver for the SpacemiT K1 SoC APMU clock control unit. + * + * Written using SpacemiT's K1 Chip Product Documentation, + * Section 9., "Top System (1/2)" and the accompanying clock + * tree schematic, available at https://www.spacemit.com/community/document/ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/resource.h> +#include <sys/rman.h> + +#include <machine/bus.h> + +#include <dev/fdt/simplebus.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/clk/clk.h> +#include <dev/hwreset/hwreset.h> + +#include <dt-bindings/clock/spacemit,k1-syscon.h> + +#include "k1_clk.h" + +#define K1_SDH0_CLK_RES_CTRL 0x54 +#define K1_SDH1_CLK_RES_CTRL 0x58 +#define K1_SDH2_CLK_RES_CTRL 0xE0 +#define K1_PMUA_ACLK_CTRL 0x388 + +static const struct k1_clk_def apmu_clks[] = { +K1_CLK_DIV_MUX(pmua_aclk, CLK_PMUA_ACLK, K1_PMUA_ACLK_CTRL, + 1, 0, 2, 1, 4, + "pll1_d10_aud", "pll1_d8"), + +K1_CLK_GATE(sdh_axi_aclk, CLK_SDH_AXI, K1_SDH0_CLK_RES_CTRL, 4, + "pmua_aclk"), + +K1_CLK_DIV_MUX_GATE(sdh0_clk, CLK_SDH0, K1_SDH0_CLK_RES_CTRL, + 3, 8, 3, 5, 4, 11, + "pll1_d6", "pll1_d4", "pll2_d8", "pll2_d5", + "pll1_d11", "pll1_d11" , "pll1_d13", "pll1_d23"), + +K1_CLK_DIV_MUX_GATE(sdh1_clk, CLK_SDH1, K1_SDH1_CLK_RES_CTRL, + 3, 8, 3, 5, 4, 11, + "pll1_d6", "pll1_d4", "pll2_d8", "pll2_d5", + "pll1_d11", "pll1_d11" , "pll1_d13", "pll1_d23"), + +K1_CLK_DIV_MUX_GATE(sdh2_clk, CLK_SDH2, K1_SDH2_CLK_RES_CTRL, + 3, 8, 3, 5, 4, 11, + "pll1_d6", "pll1_d4", "pll2_d8", "pll1_d3", + "pll1_d11", "pll1_d11" , "pll1_d13", "pll1_d23") +}; + +static const struct k1_reset_def apmu_resets[] = { +K1_RESET(RESET_SDH_AXI, K1_SDH0_CLK_RES_CTRL, 0, 3), +K1_RESET(RESET_SDH0, K1_SDH0_CLK_RES_CTRL, 0, 1), +K1_RESET(RESET_SDH1, K1_SDH1_CLK_RES_CTRL, 0, 1), +K1_RESET(RESET_SDH2, K1_SDH2_CLK_RES_CTRL, 0, 1), +}; + +#define K1_APMU_NRESETS (RESET_SDH2 + 1); + +static int +k1_apmu_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + if (!ofw_bus_is_compatible(dev, "spacemit,k1-syscon-apmu")) + return (ENXIO); + device_set_desc(dev, "SpacemitT K1 APMU Clock Controller"); + + return (BUS_PROBE_DEFAULT); +} + +static int +k1_apmu_attach(device_t dev) +{ + struct k1_clkdev_softc *sc; + + sc = device_get_softc(dev); + sc->clks = apmu_clks; + sc->nclks = nitems(apmu_clks); + + sc->resets = apmu_resets; + sc->nresets = K1_APMU_NRESETS; + + return (k1_clk_attach(dev)); +} + +static device_method_t k1_apmu_clkdev_methods[] = { + /* device_if */ + DEVMETHOD(device_probe, k1_apmu_probe), + DEVMETHOD(device_attach, k1_apmu_attach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_1(k1_apmu, k1_apmu_driver, k1_apmu_clkdev_methods, + sizeof(struct k1_clkdev_softc), k1_clkdev_driver); +EARLY_DRIVER_MODULE(k1_apmu, simplebus, k1_apmu_driver, 0, 0, + BUS_PASS_BUS + BUS_PASS_ORDER_LAST); diff --git a/sys/dev/clk/spacemit/k1_clk.c b/sys/dev/clk/spacemit/k1_clk.c new file mode 100644 index 000000000000..3ee1ae66c1b5 --- /dev/null +++ b/sys/dev/clk/spacemit/k1_clk.c @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2026 Bojan Novković <bnovkov@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Generic clock routines for the SpacemiT K1 SoC. + * + * Written using SpacemiT's K1 Chip Product Documentation, + * Section 9., "Top System (1/2)", available at + * https://www.spacemit.com/community/document/ + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/mutex.h> + +#include <machine/bus.h> +#include <sys/rman.h> +#include <machine/resource.h> + +#include <dev/fdt/simplebus.h> + +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/clk/clk.h> + +#include <dev/hwreset/hwreset.h> + +#include "k1_clk.h" +#include "clkdev_if.h" +#include "hwreset_if.h" + +#define K1_CLK_FCREQ_NRETRIES 5 + +#define DEVICE_LOCK(_clk) \ + CLKDEV_DEVICE_LOCK(clknode_get_device(_clk)) +#define DEVICE_UNLOCK(_clk) \ + CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk)) + +#define READ4(sc, reg) bus_read_4((sc)->res, (reg)) +#define WRITE4(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) + +static int +k1_clk_register(struct clkdom *clkdom, + const struct k1_clk_def *clkdef, clknode_class_t class) +{ + struct k1_clk_def *sc; + struct clknode *clk; + + clk = clknode_create(clkdom, class, + &clkdef->clkdef); + if (clk == NULL) + return (1); + + sc = clknode_get_softc(clk); + *sc = *clkdef; + clknode_register(clkdom, clk); + + return (0); +} + +static void +k1_clkdev_lock(device_t dev) +{ + struct k1_clkdev_softc *sc; + + sc = device_get_softc(dev); + mtx_lock(&sc->mtx); +} + +static void +k1_clkdev_unlock(device_t dev) +{ + struct k1_clkdev_softc *sc; + + sc = device_get_softc(dev); + mtx_unlock(&sc->mtx); +} + +int +k1_clk_attach(device_t dev) +{ + struct k1_clkdev_softc *sc; + clknode_class_t class; + int error, rid, i; + + sc = device_get_softc(dev); + mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); + + if (sc->resets != NULL) + hwreset_register_ofw_provider(dev); + + sc->clkdom = clkdom_create(dev); + if (sc->clkdom == NULL) { + device_printf(dev, "Couldn't create clkdom\n"); + return (ENXIO); + } + + rid = 0; + sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, + &rid, RF_ACTIVE); + if (sc->res == NULL) { + device_printf(dev, "Can't allocate memory\n"); + return (ENXIO); + } + + class = sc->clknode_class != NULL ? + sc->clknode_class : &k1_clknode_class; + for (i = 0; i < sc->nclks; i++) { + error = k1_clk_register(sc->clkdom, &sc->clks[i], class); + if (error != 0) { + device_printf(dev, "Failed to register clock '%s'\n", + sc->clks[i].clkdef.name); + bus_free_resource(dev, SYS_RES_MEMORY, sc->res); + return (error); + } + } + + error = clkdom_finit(sc->clkdom); + if (error != 0) { + device_printf(dev, "clkdom_finit() returned %d\n", error); + bus_free_resource(dev, SYS_RES_MEMORY, sc->res); + return (error); + } + + if (bootverbose) + clkdom_dump(sc->clkdom); + + return (0); +} + +static int +k1_clkdev_reset_assert(device_t dev, intptr_t id, bool assert) +{ + struct k1_clkdev_softc *sc; + const struct k1_reset_def *rdef; + uint32_t val; + + sc = device_get_softc(dev); + + if (id >= sc->nresets) + return (0); + rdef = &sc->resets[id]; + if (rdef->reg == 0) + return (0); + + mtx_lock(&sc->mtx); + val = READ4(sc, rdef->reg); + if (assert) + val |= rdef->assert_mask; + else + val &= ~rdef->deassert_mask; + WRITE4(sc, rdef->reg, val); + mtx_unlock(&sc->mtx); + + return (0); +} + +static device_method_t k1_clkdev_methods[] = { + /* clkdev interface */ + DEVMETHOD(clkdev_device_lock, k1_clkdev_lock), + DEVMETHOD(clkdev_device_unlock, k1_clkdev_unlock), + + /* Reset interface */ + DEVMETHOD(hwreset_assert, k1_clkdev_reset_assert), + + DEVMETHOD_END +}; +DEFINE_CLASS_0(k1_clkdev, k1_clkdev_driver, k1_clkdev_methods, + sizeof(struct k1_clkdev_softc)); + +static int +k1_clk_set_gate(struct clknode *clk, bool enable) +{ + struct k1_clkdev_softc *sc; + struct k1_clk_def *clk_sc; + uint32_t reg; + + sc = device_get_softc(clknode_get_device(clk)); + clk_sc = clknode_get_softc(clk); + if (clk_sc->gate_mask == 0 || clk_sc->reg == 0) + return (0); + + DEVICE_LOCK(clk); + reg = READ4(sc, clk_sc->reg); + if (enable) + reg |= clk_sc->gate_mask; + else + reg &= ~clk_sc->gate_mask; + WRITE4(sc, clk_sc->reg, reg); + DEVICE_UNLOCK(clk); + + return (0); +} + +static bool +k1_clk_rounding_check(uint64_t curfreq, uint64_t req, int flags) +{ + switch (CLK_SET_ROUND(flags)) { + case CLK_SET_ROUND_DOWN: + return curfreq <= req; + case CLK_SET_ROUND_UP: + return curfreq >= req; + case CLK_SET_ROUND_EXACT: + /* CLK_SET_ROUND_EXACT is enforced in 'k1_clk_set_freq'. */ + case CLK_SET_ROUND_ANY: + default: + return true; + } +} + +/* + * Find the closest matching rate given the requested frequency 'req' and + * rounding mode in 'flags' by going through all possible combinations of + * frequency divisor values and parent clock rates. + */ +static uint64_t +k1_clk_find_best_rate(struct clknode *clk, struct k1_clk_def *sc, uint64_t req, + int flags, int *parentp, uint32_t *divp) +{ + uint64_t best_match, best_delta, p_freq, candidate_freq; + uint32_t i, divmax, best_div; + struct clknode *p_clk; + const char **p_names; + int best_parent; + int p_idx; + + best_div = 1; + best_match = 0; + best_parent = 0; + best_delta = UINT64_MAX; + p_names = clknode_get_parent_names(clk); + for (p_idx = 0; p_idx != clknode_get_parents_num(clk); p_idx++) { + p_clk = clknode_find_by_name(p_names[p_idx]); + clknode_get_freq(p_clk, &p_freq); + + divmax = 1 << sc->div_nbits; + for (i = 1; i <= divmax; i++) { + candidate_freq = p_freq / i; + if (clk_freq_diff(req, candidate_freq) < best_delta && + k1_clk_rounding_check(candidate_freq, req, flags)) { + best_match = candidate_freq; + best_parent = p_idx; + best_delta = clk_freq_diff(req, candidate_freq); + best_div = i; + } + } + } + + *parentp = best_parent; + *divp = best_div; + + return (best_match); +} + +/* + * Certain clocks must perform a "frequency change request" after + * reparenting or changing the clock's frequency divisor. This is done + * by setting the appropriate FC bit in the clock's configuration + * register and waiting until the hardware clears it, indicating + * that the clock's divisor or parent index was successfully updated. + */ +static int +k1_clk_send_fc_req(struct clknode *clk) +{ + struct k1_clkdev_softc *sc; + struct k1_clk_def *clk_sc; + uint32_t val; + int retries; + + sc = device_get_softc(clknode_get_device(clk)); + clk_sc = clknode_get_softc(clk); + + if (clk_sc->fc_mask == 0) + return (0); + + retries = K1_CLK_FCREQ_NRETRIES; + val = READ4(sc, clk_sc->reg); + val |= clk_sc->fc_mask; + WRITE4(sc, clk_sc->reg, val); + while (retries-- > 0) { + DELAY(1000); + val = READ4(sc, clk_sc->reg); + if ((val & clk_sc->fc_mask) == 0) + break; + } + if ((val & clk_sc->fc_mask) != 0) { + /* The FC bit is still set, something went wrong. */ + return (ENXIO); + } + + return (0); +} + +static int +k1_clk_set_freq(struct clknode *clk, uint64_t fparent, uint64_t *fout, + int flags, int *stop) +{ + uint32_t div_mask, mux_mask; + int best_div, best_parent; + struct k1_clkdev_softc *sc; + struct k1_clk_def *clk_sc; + uint64_t best_match; + uint32_t val; + device_t dev; + int p_idx; + + dev = clknode_get_device(clk); + sc = device_get_softc(clknode_get_device(clk)); + clk_sc = clknode_get_softc(clk); + p_idx = clknode_get_parent_idx(clk); + + best_match = k1_clk_find_best_rate(clk, clk_sc, *fout, flags, + &best_parent, &best_div); + *stop = 1; + if (best_match == 0 || + (best_match != *fout && + CLK_SET_ROUND(flags) == CLK_SET_ROUND_EXACT)) + return (ERANGE); + + *fout = best_match; + if ((flags & CLK_SET_DRYRUN) != 0) + return (0); + + DEVICE_LOCK(clk); + if (clk_sc->mux_nbits != 0 && p_idx != best_parent) { + + clknode_set_parent_by_idx(clk, best_parent); + mux_mask = (1 << clk_sc->mux_nbits) - 1; + mux_mask <<= clk_sc->mux_shift; + val = READ4(sc, clk_sc->reg); + val &= ~mux_mask; + val |= (p_idx << clk_sc->mux_shift); + WRITE4(sc, clk_sc->reg, val); + + if (k1_clk_send_fc_req(clk) != 0) { + device_printf(dev, + "%s: failed mux frequency change request for '%s'\n", + __func__, clk_sc->clkdef.name); + DEVICE_UNLOCK(clk); + return (ENXIO); + } + } + + if (clk_sc->div_nbits != 0) { + div_mask = (1 << clk_sc->div_nbits) - 1; + val = READ4(sc, clk_sc->reg); + val &= ~(div_mask << clk_sc->div_shift); + + /* The final divisor value is calculated as 'div_value' + 1. */ + val |= (best_div - 1) << clk_sc->div_shift; + WRITE4(sc, clk_sc->reg, val); + + if (k1_clk_send_fc_req(clk) != 0) { + device_printf(dev, + "%s: failed div frequency change request for '%s'\n", + __func__, clk_sc->clkdef.name); + DEVICE_UNLOCK(clk); + return (ENXIO); + } + } + DEVICE_UNLOCK(clk); + + return (0); +} + +static int +k1_clk_init(struct clknode *clk, device_t dev) +{ + struct k1_clkdev_softc *sc; + struct k1_clk_def *clk_sc; + uint32_t val, mux_mask; + int pidx; + + sc = device_get_softc(clknode_get_device(clk)); + clk_sc = clknode_get_softc(clk); + + if (clk_sc->mux_shift == 0) { + clknode_init_parent_idx(clk, 0); + return (0); + } + DEVICE_LOCK(clk); + val = READ4(sc, clk_sc->reg); + DEVICE_UNLOCK(clk); + mux_mask = (1 << clk_sc->mux_nbits) - 1; + pidx = (val >> clk_sc->mux_shift) & mux_mask; + + clknode_init_parent_idx(clk, pidx); + + return (0); +} + +static clknode_method_t k1_clknode_methods[] = { + CLKNODEMETHOD(clknode_init, k1_clk_init), + CLKNODEMETHOD(clknode_set_gate, k1_clk_set_gate), + CLKNODEMETHOD(clknode_set_freq, k1_clk_set_freq), + CLKNODEMETHOD_END +}; + +DEFINE_CLASS_1(k1_clknode, k1_clknode_class, + k1_clknode_methods, sizeof(struct k1_clk_def), clknode_class); diff --git a/sys/dev/clk/spacemit/k1_clk.h b/sys/dev/clk/spacemit/k1_clk.h new file mode 100644 index 000000000000..384e3aa8467e --- /dev/null +++ b/sys/dev/clk/spacemit/k1_clk.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2026 Bojan Novković <bnovkov@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + + +/* + * Common helper macros for the Spacemit K1 SoC clocks. + */ + +#ifndef _K1_CLK_H_ +#define _K1_CLK_H_ + +#include <dev/clk/clk.h> + +/* + * Helper macro to count the number of parent clocks passed + * to the K1_CLK_COMMON macro. The maximum number of parents + * for the K1 clocks is 8. + */ +#define _K1_CLK_PARENT_CNT_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, N, ...) N +#define K1_CLK_PARENT_CNT(...) _K1_CLK_PARENT_CNT_IMPL(_, ## __VA_ARGS__, 8, \ + 7, 6, 5, 4, 3, 2, 1, 0) + +#define K1_CLK_COMMON(_clkname, _id, ...) \ + .clkdef = { \ + .id = _id, \ + .name = #_clkname, \ + .parent_names = (const char*[]) { __VA_ARGS__ }, \ + .parent_cnt = K1_CLK_PARENT_CNT(__VA_ARGS__), \ +} + +#define K1_CLK_GATE(_clkname, _id, _reg, _gate_bit, ...) \ + { \ + K1_CLK_COMMON(_clkname, _id, __VA_ARGS__), \ + .reg = _reg, \ + .gate_mask = 1 << _gate_bit, \ +} + +#define K1_CLK_DIV(_clkname, _id, _reg, _div_bits, _div_shift, \ + _fc_bit, ...) \ + { \ + K1_CLK_COMMON(_clkname, _id, __VA_ARGS__), \ + .reg = _reg, \ + .div_nbits = _div_bits, \ + .div_shift = _div_shift, \ + .fc_mask = 1 << _fc_bit, \ +} + +#define K1_CLK_DIV_MUX(_clkname, _id, _reg, _div_bits, \ + _div_shift, _mux_nbits, _mux_shift, _fc_bit, ...) \ + { \ + K1_CLK_COMMON(_clkname, _id, __VA_ARGS__), \ + .reg = _reg, \ + .div_nbits = _div_bits, \ + .div_shift = _div_shift, \ + .mux_nbits = _mux_nbits, \ + .mux_shift = _mux_shift, \ + .fc_mask = 1 <<_fc_bit, \ +} + +#define K1_CLK_DIV_GATE(_clkname, _id, _reg, _div_bits, _div_shift, \ + _gate_bit, ...) \ + { \ + K1_CLK_COMMON(_clkname, _id, __VA_ARGS__), \ + .reg = _reg, \ + .gate_mask = (1 << _gate_bit), \ + .div_nbits = _div_bits, \ + .div_shift = _div_shift, \ +} + +#define K1_CLK_MUX_GATE(_clkname, _id, _reg, _mux_nbits, _mux_shift, \ + _gate_bit, ...) \ + { \ + K1_CLK_COMMON(_clkname, _id, __VA_ARGS__), \ + .reg = _reg, \ + .gate_mask = (1 << _gate_bit), \ + .mux_nbits = _mux_nbits, \ + .mux_shift = _mux_shift, \ +} + +#define K1_CLK_DIV_MUX_GATE(_clkname, _id, _reg, _div_bits, \ + _div_shift, _mux_nbits, _mux_shift, \ + _gate_bit, _fc_bit, ...) \ + { \ + K1_CLK_COMMON(_clkname, _id, __VA_ARGS__), \ + .reg = _reg, \ + .gate_mask = (1 << _gate_bit), \ + .div_nbits = _div_bits, \ + .div_shift = _div_shift, \ + .mux_nbits = _mux_nbits, \ + .mux_shift = _mux_shift, \ + .fc_mask = 1 <<_fc_bit, \ +} + +#define K1_RESET(_id, _reg, _assert_bit, _deassert_bit) \ + [_id] = { \ + .reg = _reg, \ + .assert_mask = (1 << _assert_bit), \ + .deassert_mask = (1 << _deassert_bit), \ +} + +struct k1_clk_def { + struct clknode_init_def clkdef; + + uint64_t rate; /* Fixed clock frequency. */ + uint32_t fixed_div; /* Fixed clock divisor. */ + uint32_t reg; /* Control register offset.*/ + uint32_t div_nbits; /* Frequency divisor. */ + uint32_t div_shift; + uint32_t gate_mask; /* Gate bit. */ + uint32_t mux_nbits; /* Multiplexer offset. */ + uint32_t mux_shift; + uint32_t fc_mask; /* Frequency change request bit. */ +}; + +struct k1_reset_def { + uint32_t reg; + uint32_t assert_mask; + uint32_t deassert_mask; +}; + +struct k1_clkdev_softc { + struct mtx mtx; + struct clkdom *clkdom; + struct resource *res; + + const struct k1_clk_def *clks; + const struct k1_reset_def *resets; + int nclks; + int nresets; + + /* + * Allow derived clknode implementations to + * override the generic K1 clock routines. + */ + clknode_class_t clknode_class; +}; + +DECLARE_CLASS(k1_clkdev_driver); +DECLARE_CLASS(k1_clknode_class); + +int k1_clk_attach(device_t dev); + +#endif diff --git a/sys/dev/clk/spacemit/k1_pll.c b/sys/dev/clk/spacemit/k1_pll.c new file mode 100644 index 000000000000..c90eab0789bc --- /dev/null +++ b/sys/dev/clk/spacemit/k1_pll.c @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2026 Bojan Novković <bnovkov@FreeBSD.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Driver for the SpacemiT K1 SoC PLL clocks. + * + * Written using SpacemiT's K1 Chip Product Documentation, + * Section 9., "Top System (1/2)" and the accompanying clock + * tree schematic, available at https://www.spacemit.com/community/document/ + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/mutex.h> + +#include <machine/bus.h> +#include <sys/rman.h> +#include <machine/resource.h> + +#include <dev/fdt/simplebus.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <dev/clk/clk.h> + +#include <dt-bindings/clock/spacemit,k1-syscon.h> + +#include "clkdev_if.h" +#include "k1_clk.h" + +#define K1_PLL1_SW2_CTRL 0x110 +#define K1_PLL2_SW2_CTRL 0x11C +#define K1_PLL3_SW2_CTRL 0x128 + +#define K1_PLL_ROOT(_clkname, _id, _rate) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = #_clkname, \ + }, \ + .rate = _rate, \ +} + +#define K1_PLL_DIV(_clkname, _id, _div, ...) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = #_clkname, \ + .parent_names = (const char * []) { __VA_ARGS__ }, \ + .parent_cnt = K1_CLK_PARENT_CNT(__VA_ARGS__), \ + }, \ + .fixed_div = _div, \ +} + +#define K1_PLL_DIV_GATE(_clkname, _id, _div, _reg, _gate_bit, ...) \ + { \ + .clkdef = { \ + .id = _id, \ + .name = #_clkname, \ + .parent_names = (const char * []) { __VA_ARGS__ }, \ + .parent_cnt = K1_CLK_PARENT_CNT(__VA_ARGS__), \ + }, \ + .fixed_div = _div, \ + .reg = _reg, \ + .gate_mask = (1 << _gate_bit), \ +} + +static struct k1_clk_def k1_plls[] = { + K1_PLL_ROOT(pll1, CLK_PLL1, 2457600000UL), + K1_PLL_ROOT(pll2, CLK_PLL2, 3000000000UL), + K1_PLL_ROOT(pll3, CLK_PLL3, 3200000000UL), + + K1_PLL_DIV_GATE(pll1_d2, CLK_PLL1_D2, 2, K1_PLL1_SW2_CTRL, 1, "pll1"), + K1_PLL_DIV_GATE(pll1_d3, CLK_PLL1_D3, 3, K1_PLL1_SW2_CTRL, 2, "pll1"), + K1_PLL_DIV_GATE(pll1_d4, CLK_PLL1_D4, 4, K1_PLL1_SW2_CTRL, 3, "pll1"), + K1_PLL_DIV_GATE(pll1_d5, CLK_PLL1_D5, 5, K1_PLL1_SW2_CTRL, 4, "pll1"), + K1_PLL_DIV_GATE(pll1_d6, CLK_PLL1_D6, 6, K1_PLL1_SW2_CTRL, 5, "pll1"), + K1_PLL_DIV_GATE(pll1_d7, CLK_PLL1_D7, 7, K1_PLL1_SW2_CTRL, 6, "pll1"), + K1_PLL_DIV_GATE(pll1_d8, CLK_PLL1_D8, 8, K1_PLL1_SW2_CTRL, 7, "pll1"), + K1_PLL_DIV_GATE(pll1_d11, CLK_PLL1_D11, 11, K1_PLL1_SW2_CTRL, 15, "pll1"), + K1_PLL_DIV_GATE(pll1_d13, CLK_PLL1_D13, 13, K1_PLL1_SW2_CTRL, 16, "pll1"), + K1_PLL_DIV_GATE(pll1_d23, CLK_PLL1_D23, 23, K1_PLL1_SW2_CTRL, 20, "pll1"), + K1_PLL_DIV_GATE(pll1_d64, CLK_PLL1_D64, 64, K1_PLL1_SW2_CTRL, 0, "pll1"), + K1_PLL_DIV_GATE(pll1_d10_aud, CLK_PLL1_D10_AUD, 10, K1_PLL1_SW2_CTRL, 10, "pll1"), + K1_PLL_DIV_GATE(pll1_d100_aud, CLK_PLL1_D100_AUD, 100, K1_PLL1_SW2_CTRL, 11, "pll1"), + + K1_PLL_DIV_GATE(pll2_d2, CLK_PLL2_D2, 2, K1_PLL2_SW2_CTRL, 1, "pll2"), + K1_PLL_DIV_GATE(pll2_d3, CLK_PLL2_D3, 3, K1_PLL2_SW2_CTRL, 2, "pll2"), + K1_PLL_DIV_GATE(pll2_d4, CLK_PLL2_D4, 4, K1_PLL2_SW2_CTRL, 3, "pll2"), + K1_PLL_DIV_GATE(pll2_d5, CLK_PLL2_D5, 5, K1_PLL2_SW2_CTRL, 4, "pll2"), + K1_PLL_DIV_GATE(pll2_d6, CLK_PLL2_D6, 6, K1_PLL2_SW2_CTRL, 5, "pll2"), + K1_PLL_DIV_GATE(pll2_d7, CLK_PLL2_D7, 7, K1_PLL2_SW2_CTRL, 6, "pll2"), + K1_PLL_DIV_GATE(pll2_d8, CLK_PLL2_D8, 8, K1_PLL2_SW2_CTRL, 7, "pll2"), + + K1_PLL_DIV_GATE(pll3_d2, CLK_PLL3_D2, 2, K1_PLL3_SW2_CTRL, 1, "pll3"), + K1_PLL_DIV_GATE(pll3_d3, CLK_PLL3_D3, 3, K1_PLL3_SW2_CTRL, 2, "pll3"), + K1_PLL_DIV_GATE(pll3_d4, CLK_PLL3_D4, 4, K1_PLL3_SW2_CTRL, 3, "pll3"), + K1_PLL_DIV_GATE(pll3_d5, CLK_PLL3_D5, 5, K1_PLL3_SW2_CTRL, 4, "pll3"), + K1_PLL_DIV_GATE(pll3_d6, CLK_PLL3_D6, 6, K1_PLL3_SW2_CTRL, 5, "pll3"), + K1_PLL_DIV_GATE(pll3_d7, CLK_PLL3_D7, 7, K1_PLL3_SW2_CTRL, 6, "pll3"), + K1_PLL_DIV_GATE(pll3_d8, CLK_PLL3_D8, 8, K1_PLL3_SW2_CTRL, 7, "pll3"), + K1_PLL_DIV(pll3_80, CLK_PLL3_80, 20, "pll3_d8"), + K1_PLL_DIV(pll3_40, CLK_PLL3_40, 10, "pll3_d8"), + K1_PLL_DIV(pll3_20, CLK_PLL3_20, 5, "pll3_d8"), +}; + +/* + * The SpacemiT K1 manual states that "Changes of the run-time frequency + * in the PLL{1,2,3} output are only available for debugging purposes and + * should not be used in production systems", which is why this driver does + * not support changing the top-level PLL frequency and instead treats all + * PLLs as gateable fixed clocks. + */ +static int +k1_pll_recalc_freq(struct clknode *clk, uint64_t *freq) +{ + struct k1_clk_def *sc; + + sc = clknode_get_softc(clk); + if (sc->fixed_div != 0) + *freq = *freq / sc->fixed_div; + else + *freq = sc->rate; + return (0); +} + +static int +k1_pll_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout, + int flags, int *stop) +{ + struct k1_clk_def *sc; + + sc = clknode_get_softc(clk); + if (sc->fixed_div == 0) { + /* Top-level PLL */ + *stop = 1; + if (*fout != sc->rate) + return (ERANGE); + return (0); + } + *stop = 0; + *fout = *fout / sc->fixed_div; + + return (0); +} + +static clknode_method_t k1_pll_clknode_methods[] = { + CLKNODEMETHOD(clknode_set_freq, k1_pll_set_freq), + CLKNODEMETHOD(clknode_recalc_freq, k1_pll_recalc_freq), + CLKNODEMETHOD_END +}; + +DEFINE_CLASS_1(k1_pll_clknode, k1_pll_clknode_class, k1_pll_clknode_methods, + sizeof(struct k1_clk_def), k1_clknode_class); + +static int +k1_pll_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "spacemit,k1-pll")) + return (ENXIO); + device_set_desc(dev, "SpacemiT K1 PLL Clock Control Unit"); + + return (BUS_PROBE_DEFAULT); +} + +static int +k1_pll_attach(device_t dev) +{ + struct k1_clkdev_softc *sc; + + sc = device_get_softc(dev); + sc->clks = k1_plls; + sc->nclks = nitems(k1_plls); + sc->clknode_class = &k1_pll_clknode_class; + + return (k1_clk_attach(dev)); +} + +static device_method_t k1_pll_methods[] = { + DEVMETHOD(device_probe, k1_pll_probe), + DEVMETHOD(device_attach, k1_pll_attach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_1(k1_pll, k1_pll_driver, k1_pll_methods, + sizeof(struct k1_clkdev_softc), k1_clkdev_driver); +EARLY_DRIVER_MODULE(k1_pll, simplebus, k1_pll_driver, 0, 0, + BUS_PASS_BUS + BUS_PASS_ORDER_EARLY); diff --git a/sys/riscv/conf/std.spacemit b/sys/riscv/conf/std.spacemit index 3cbbda20c765..bc3b2a6f9b8b 100644 --- a/sys/riscv/conf/std.spacemit +++ b/sys/riscv/conf/std.spacemit @@ -4,6 +4,7 @@ device fdt device sdhci_spacemit +device k1_ccu makeoptions MODULES_EXTRA+="dtb/spacemit" diff --git a/sys/riscv/spacemit/files.spacemit b/sys/riscv/spacemit/files.spacemit index e69de29bb2d1..b4ab14e9240b 100644 --- a/sys/riscv/spacemit/files.spacemit +++ b/sys/riscv/spacemit/files.spacemit @@ -0,0 +1,3 @@ +dev/clk/spacemit/k1_clk.c optional k1_ccu fdt +dev/clk/spacemit/k1_pll.c optional k1_ccu fdt +dev/clk/spacemit/k1_apmu.c optional k1_ccu fdt |
