From nobody Sun Aug 16 17:17:12 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hNN1j2RFNz6pLWG for ; Sun, 16 Aug 2026 17:17:13 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hNN1j0XM5z3Y0n for ; Sun, 16 Aug 2026 17:17:13 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786900633; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jBEcB6g4xdgS2j4QzJ9xrTfptrmWsXgzv5PXQX2vpQ8=; b=gm2NzouwWOGGDaofUSeyNpQozuJe83XXyjH/5PlnqXj9+GUYDk5sLsrCXigSNDMobI6vKQ HaUDbes8ogvpBnBm/U8fc0th9lAYqD5DqYurqyLg9QodXEaojen/AvliXjtb5ivHEtRMlH a5F/zPh2gn19TX/W4RTr0Ss+SXRpypWaGL8Yo2211/Y/coc6oJ9acJcQVyJGpTfazw2UWz HKJ6Z0K8E00QV1Fo8TCRQjJhUGV7koJB2rKADkQxToe5zP7e/8I2LpkrZaZ1wWODZmAPB7 /0CJxhyjII/ujtn1K6ItdH2aN+QigH/frl6YcmextomuZawJAJ1A11g50NYtZQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1786900633; a=rsa-sha256; cv=none; b=mebADF44BoQYra1x3+hCcFvCUWKYweEKLYKrnWIJs6i03XxceG/4+SzvVp5w9TRVYpkO+V wL0b/0/ie0i1VHORz24HN0JwpaYFnjl6AgOo2gucgurPxUYAeCYXm4bSrKL9R8nN4RuLRT sWGtK0UK6QSGNY157TrfUp0q+zILtkhp42yZ6diGIHUewkc4/xI2ic7B9SrBqgSuWSqjz8 sAAQjEzd8bQDwtmlFkwj51j6YgNDGNTog0dqVsfjP5KI+rUrZXiyTONdPuz3KIxvt2ccdV cervUP63BYv+ssFV8VNQZp1dPRYCYtCXXgan8lZiUEX4kRfnEfYcJsuagLKdqA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1786900633; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=jBEcB6g4xdgS2j4QzJ9xrTfptrmWsXgzv5PXQX2vpQ8=; b=LdO13o8wGVebwgwq4VAiNsZAc7G6wWK6ohaF5ZyqqVJ56rqkeQ1PST5LDJaOe2uwBItdiZ iLnf8WmskAehfqsfpQHxgfpbplHMO2Bx+BvyLV0Sxu20pJ5TztAL2NXlVPJ0yi2BPCOv09 YR81vFEFsPbK6fQ+uAHHbVlEXneuDYddCX45odhQWIGTkxwULXIFfGlQlN33fJmOcNFsA+ vHkM/kD6IGaEB7UlOLYSUyA+AZP3kFHcQ/YBf18LbflMHVKjF6is03r5iStINkEA5gpkp0 wf4rq7vFxtvX6wRLBXzIiYtoGVhuGy6JqeksmEHCdiVc9ouJa1nuAfNzcorSDA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hNN1h64mrzd9p for ; Sun, 16 Aug 2026 17:17:12 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 323a7 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Sun, 16 Aug 2026 17:17:12 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Bojan Novk=?utf-8?Q?ovi=C4=87?= Subject: git: dcb10e3add17 - main - clk: Initial support for the SpacemiT K1 clock control units List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: bnovkov X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: dcb10e3add17259715352885dfc55ca92061d2ed Auto-Submitted: auto-generated Date: Sun, 16 Aug 2026 17:17:12 +0000 Message-Id: <6a81f098.323a7.62ee2401@gitrepo.freebsd.org> The branch main has been updated by bnovkov: URL: https://cgit.FreeBSD.org/src/commit/?id=dcb10e3add17259715352885dfc55ca92061d2ed commit dcb10e3add17259715352885dfc55ca92061d2ed Author: Bojan Novković AuthorDate: 2026-05-15 09:13:13 +0000 Commit: Bojan Novković CommitDate: 2026-08-16 17:14:19 +0000 clk: Initial support for the SpacemiT K1 clock control units This change implements the core clknode methods for the SpacemiT K1 clock control units. These methods were used to implement drivers for the APMU and PLL CCUs. The initial driver for the APMU CCU only contains clock definitions for the SDHCI controller for now. Differential Revision: https://reviews.freebsd.org/D57176 Reviewed by: mmel, mhorne, manu --- sys/dev/clk/spacemit/k1_apmu.c | 114 +++++++++++ sys/dev/clk/spacemit/k1_clk.c | 409 ++++++++++++++++++++++++++++++++++++++ sys/dev/clk/spacemit/k1_clk.h | 146 ++++++++++++++ sys/dev/clk/spacemit/k1_pll.c | 200 +++++++++++++++++++ sys/riscv/conf/std.spacemit | 1 + sys/riscv/spacemit/files.spacemit | 3 + 6 files changed, 873 insertions(+) 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ć + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#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ć + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#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ć + * + * SPDX-License-Identifier: BSD-2-Clause + */ + + +/* + * Common helper macros for the Spacemit K1 SoC clocks. + */ + +#ifndef _K1_CLK_H_ +#define _K1_CLK_H_ + +#include + +/* + * 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ć + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#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