Page MenuHomeFreeBSD

D57176.id178420.diff
No OneTemporary

D57176.id178420.diff

diff --git a/sys/dev/clk/spacemit/k1_apmu.c b/sys/dev/clk/spacemit/k1_apmu.c
new file mode 100644
--- /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 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 struct k1_reset_def apmu_resets[] = {
+K1_RESET(RESET_SDH_AXI, K1_SDH0_CLK_RES_CTRL, 3),
+K1_RESET(RESET_SDH0, K1_SDH0_CLK_RES_CTRL, 1),
+K1_RESET(RESET_SDH1, K1_SDH1_CLK_RES_CTRL, 1),
+K1_RESET(RESET_SDH2, K1_SDH2_CLK_RES_CTRL, 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.h b/sys/dev/clk/spacemit/k1_clk.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/clk/spacemit/k1_clk.h
@@ -0,0 +1,143 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Bojan Novković <bnovkov@FreeBSD.org>
+ */
+
+/*
+ * 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, _bit) \
+ [_id] = { \
+ .reg = _reg, \
+ .mask = (1 << _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 mask;
+};
+
+struct k1_clkdev_softc {
+ struct mtx mtx;
+ struct clkdom *clkdom;
+ struct resource *res;
+
+ struct k1_clk_def *clks;
+ 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_clk.c b/sys/dev/clk/spacemit/k1_clk.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/clk/spacemit/k1_clk.c
@@ -0,0 +1,414 @@
+/*
+ * 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 reset)
+{
+ struct k1_clkdev_softc *sc;
+ 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 (reset)
+ val &= ~rdef->mask;
+ else
+ val |= rdef->mask;
+ WRITE4(sc, rdef->reg, val);
+ mtx_unlock(&sc->mtx);
+
+ return (0);
+}
+
+static int
+k1_clkdev_reset_is_asserted(device_t dev, intptr_t id, bool *reset)
+{
+ struct k1_clkdev_softc *sc;
+ 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);
+ *reset = (val & rdef->mask) != 0;
+ 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(hwreset_is_asserted, k1_clkdev_reset_is_asserted),
+
+ 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);
+}
+
+/*
+ * Find the closest matching rate for the requested frequency 'req' 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 *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) {
+ 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, &best_parent,
+ &best_div);
+ *stop = 1;
+
+ if ((flags & CLK_SET_ROUND_DOWN) == 0 && best_match < *fout)
+ return (ERANGE);
+ if ((flags & CLK_SET_ROUND_UP) == 0 && best_match > *fout)
+ return (ERANGE);
+ *fout = best_match;
+
+ 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_pll.c b/sys/dev/clk/spacemit/k1_pll.c
new file mode 100644
--- /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
--- 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
--- 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

File Metadata

Mime Type
text/plain
Expires
Wed, May 27, 12:46 PM (47 m, 58 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
33553090
Default Alt Text
D57176.id178420.diff (23 KB)

Event Timeline