Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F157922129
D57176.id178420.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
23 KB
Referenced Files
None
Subscribers
None
D57176.id178420.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D57176: clk: Initial support for the SpacemiT K1 clock control units
Attached
Detach File
Event Timeline
Log In to Comment