git: e32b082ba28b - main - clk: rockchip: Add the MMC phase clock

From: Emmanuel Vadot <manu_at_FreeBSD.org>
Date: Fri, 30 Oct 2026 07:36:59 UTC
The branch main has been updated by manu:

URL: https://cgit.FreeBSD.org/src/commit/?id=e32b082ba28bd3d0e026782eeb93f124ced7508f

commit e32b082ba28bd3d0e026782eeb93f124ced7508f
Author:     Emmanuel Vadot <manu@FreeBSD.org>
AuthorDate: 2026-10-20 08:09:18 +0000
Commit:     Emmanuel Vadot <manu@FreeBSD.org>
CommitDate: 2026-10-30 07:36:46 +0000

    clk: rockchip: Add the MMC phase clock
    
    Add a clock node type for the Rockchip MMC sample and drive clocks.  These
    clocks have a phase in degrees controlled by a delay line and a fine-grained
    phase selector, and implement the new clknode phase methods.
    
    Reviewed by:    mmel
    Differential Revision:  https://reviews.freebsd.org/D60886
---
 sys/conf/files.arm64              |   1 +
 sys/dev/clk/rockchip/rk_clk_mmc.c | 219 ++++++++++++++++++++++++++++++++++++++
 sys/dev/clk/rockchip/rk_clk_mmc.h |  41 +++++++
 sys/dev/clk/rockchip/rk_cru.c     |   3 +
 sys/dev/clk/rockchip/rk_cru.h     |  18 ++++
 5 files changed, 282 insertions(+)

diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64
index 105bc679acb8..2f914c4efd64 100644
--- a/sys/conf/files.arm64
+++ b/sys/conf/files.arm64
@@ -850,6 +850,7 @@ dev/clk/rockchip/rk_clk_armclk.c		optional fdt soc_rockchip
 dev/clk/rockchip/rk_clk_composite.c		optional fdt soc_rockchip
 dev/clk/rockchip/rk_clk_fract.c			optional fdt soc_rockchip
 dev/clk/rockchip/rk_clk_gate.c			optional fdt soc_rockchip
+dev/clk/rockchip/rk_clk_mmc.c			optional fdt soc_rockchip
 dev/clk/rockchip/rk_clk_mux.c			optional fdt soc_rockchip
 dev/clk/rockchip/rk_clk_pll.c			optional fdt soc_rockchip
 dev/clk/rockchip/rk3328_cru.c			optional fdt soc_rockchip_rk3328
diff --git a/sys/dev/clk/rockchip/rk_clk_mmc.c b/sys/dev/clk/rockchip/rk_clk_mmc.c
new file mode 100644
index 000000000000..8978280db86e
--- /dev/null
+++ b/sys/dev/clk/rockchip/rk_clk_mmc.c
@@ -0,0 +1,219 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2026 Emmanuel Vadot <manu@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <dev/clk/clk.h>
+
+#include <dev/clk/rockchip/rk_clk_mmc.h>
+
+#include "clkdev_if.h"
+
+#define	WR4(_clk, off, val)						\
+	CLKDEV_WRITE_4(clknode_get_device(_clk), off, val)
+#define	RD4(_clk, off, val)						\
+	CLKDEV_READ_4(clknode_get_device(_clk), off, val)
+#define	DEVICE_LOCK(_clk)						\
+	CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
+#define	DEVICE_UNLOCK(_clk)						\
+	CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
+
+/*
+ * MMC drive/sample phase clock.  The controller output clock is the parent
+ * rate divided by two (RK_MMC_CLKGEN_DIV).  The phase is programmed as a
+ * coarse 90 degree step plus an optional fine delay of up to 255 elements.
+ */
+#define	RK_MMC_CLKGEN_DIV	2
+#define	RK_MMC_DELAY_SEL	(1 << 10)
+#define	RK_MMC_DEGREE_MASK	0x3
+#define	RK_MMC_DELAYNUM_OFFSET	2
+#define	RK_MMC_DELAYNUM_MASK	(0xff << RK_MMC_DELAYNUM_OFFSET)
+#define	RK_MMC_PHASE_MASK	0x7ff
+
+/*
+ * Each fine delay element is between 44 and 77 ps; assume 60 ps.  So an
+ * angle can be off by up to about 25 degrees, which is acceptable because
+ * the tuning only needs to find a point far from the bad range.
+ */
+#define	RK_MMC_DELAY_ELEMENT_PSEC	60
+
+#define	DIV_ROUND_CLOSEST_U(n, d)	(((n) + ((d) / 2)) / (d))
+
+struct rk_clk_mmc_sc {
+	uint32_t	offset;
+	uint32_t	shift;
+};
+
+static int
+rk_clk_mmc_init(struct clknode *clk, device_t dev)
+{
+
+	clknode_init_parent_idx(clk, 0);
+	return (0);
+}
+
+static int
+rk_clk_mmc_recalc_freq(struct clknode *clk, uint64_t *freq)
+{
+
+	*freq /= RK_MMC_CLKGEN_DIV;
+	return (0);
+}
+
+static int
+rk_clk_mmc_get_phase(struct clknode *clk, int *degrees)
+{
+	struct rk_clk_mmc_sc *sc;
+	struct clknode *parent;
+	uint64_t rate;
+	uint32_t raw;
+	int rv;
+
+	sc = clknode_get_softc(clk);
+
+	/*
+	 * Read the parent rate: the core holds our node lock while calling
+	 * this method, so clknode_get_freq() on ourselves would relock it.
+	 */
+	parent = clknode_get_parent(clk);
+	if (parent == NULL) {
+		*degrees = 0;
+		return (0);
+	}
+	rv = clknode_get_freq(parent, &rate);
+	if (rv != 0)
+		return (rv);
+	rate /= RK_MMC_CLKGEN_DIV;
+	/* Constant signal, no measurable phase shift. */
+	if (rate == 0) {
+		*degrees = 0;
+		return (0);
+	}
+
+	DEVICE_LOCK(clk);
+	rv = RD4(clk, sc->offset, &raw);
+	DEVICE_UNLOCK(clk);
+	if (rv != 0)
+		return (rv);
+
+	raw >>= sc->shift;
+	*degrees = (raw & RK_MMC_DEGREE_MASK) * 90;
+	if ((raw & RK_MMC_DELAY_SEL) != 0) {
+		uint64_t factor, delay_num;
+
+		/* degrees per delay element * 1000000 */
+		factor = (RK_MMC_DELAY_ELEMENT_PSEC / 10) * 36 *
+		    (rate / 10000);
+		delay_num = (raw & RK_MMC_DELAYNUM_MASK) >>
+		    RK_MMC_DELAYNUM_OFFSET;
+		*degrees += DIV_ROUND_CLOSEST_U(delay_num * factor, 1000000);
+	}
+	*degrees %= 360;
+	return (0);
+}
+
+static int
+rk_clk_mmc_set_phase(struct clknode *clk, int degrees)
+{
+	struct rk_clk_mmc_sc *sc;
+	struct clknode *parent;
+	uint64_t rate, delay;
+	uint32_t raw, nineties, remainder;
+	int rv;
+
+	sc = clknode_get_softc(clk);
+
+	if (degrees < 0 || degrees >= 360)
+		return (EINVAL);
+	/*
+	 * Read the parent rate: the core holds our node lock while calling
+	 * this method, so clknode_get_freq() on ourselves would relock it.
+	 */
+	parent = clknode_get_parent(clk);
+	if (parent == NULL)
+		return (EINVAL);
+	rv = clknode_get_freq(parent, &rate);
+	if (rv != 0)
+		return (rv);
+	rate /= RK_MMC_CLKGEN_DIV;
+	/* The phase can't be calculated for a stopped clock. */
+	if (rate == 0)
+		return (EINVAL);
+
+	nineties = degrees / 90;
+	remainder = degrees % 90;
+
+	/* Convert the remainder to a number of fine delay elements. */
+	delay = 10000000ULL * remainder;
+	delay = DIV_ROUND_CLOSEST_U(delay, (rate / 1000) * 36 *
+	    (RK_MMC_DELAY_ELEMENT_PSEC / 10));
+	if (delay > 255)
+		delay = 255;
+
+	raw = delay != 0 ? RK_MMC_DELAY_SEL : 0;
+	raw |= delay << RK_MMC_DELAYNUM_OFFSET;
+	raw |= nineties;
+
+	/* Hiword update: the upper 16 bits are the write enable mask. */
+	DEVICE_LOCK(clk);
+	rv = WR4(clk, sc->offset, (raw << sc->shift) |
+	    ((RK_MMC_PHASE_MASK << sc->shift) << 16));
+	DEVICE_UNLOCK(clk);
+	return (rv);
+}
+
+static clknode_method_t rk_clk_mmc_methods[] = {
+	/* Device interface */
+	CLKNODEMETHOD(clknode_init,		rk_clk_mmc_init),
+	CLKNODEMETHOD(clknode_recalc_freq,	rk_clk_mmc_recalc_freq),
+	CLKNODEMETHOD(clknode_set_phase,	rk_clk_mmc_set_phase),
+	CLKNODEMETHOD(clknode_get_phase,	rk_clk_mmc_get_phase),
+	CLKNODEMETHOD_END
+};
+DEFINE_CLASS_1(rk_clk_mmc, rk_clk_mmc_class, rk_clk_mmc_methods,
+   sizeof(struct rk_clk_mmc_sc), clknode_class);
+
+int
+rk_clk_mmc_register(struct clkdom *clkdom, struct rk_clk_mmc_def *clkdef)
+{
+	struct clknode *clk;
+	struct rk_clk_mmc_sc *sc;
+
+	clk = clknode_create(clkdom, &rk_clk_mmc_class, &clkdef->clkdef);
+	if (clk == NULL)
+		return (1);
+
+	sc = clknode_get_softc(clk);
+	sc->offset = clkdef->offset;
+	sc->shift = clkdef->shift;
+
+	clknode_register(clkdom, clk);
+	return (0);
+}
diff --git a/sys/dev/clk/rockchip/rk_clk_mmc.h b/sys/dev/clk/rockchip/rk_clk_mmc.h
new file mode 100644
index 000000000000..1ae31f4f2bea
--- /dev/null
+++ b/sys/dev/clk/rockchip/rk_clk_mmc.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2026 Emmanuel Vadot <manu@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _RK_CLK_MMC_H_
+#define _RK_CLK_MMC_H_
+
+#include <dev/clk/clk.h>
+
+struct rk_clk_mmc_def {
+	struct clknode_init_def clkdef;
+	uint32_t		offset;
+	uint32_t		shift;
+};
+
+int rk_clk_mmc_register(struct clkdom *clkdom, struct rk_clk_mmc_def *clkdef);
+
+#endif /* _RK_CLK_MMC_H_ */
diff --git a/sys/dev/clk/rockchip/rk_cru.c b/sys/dev/clk/rockchip/rk_cru.c
index 6b0978e885b5..bc844d035744 100644
--- a/sys/dev/clk/rockchip/rk_cru.c
+++ b/sys/dev/clk/rockchip/rk_cru.c
@@ -263,6 +263,9 @@ rk_cru_attach(device_t dev)
 			clknode_link_register(sc->clkdom,
 			    sc->clks[i].clk.link);
 			break;
+		case RK_CLK_MMC:
+			rk_clk_mmc_register(sc->clkdom, sc->clks[i].clk.mmc);
+			break;
 		default:
 			device_printf(dev, "Unknown clock type\n");
 			return (ENXIO);
diff --git a/sys/dev/clk/rockchip/rk_cru.h b/sys/dev/clk/rockchip/rk_cru.h
index 685c6bb67568..3ad5b040dc01 100644
--- a/sys/dev/clk/rockchip/rk_cru.h
+++ b/sys/dev/clk/rockchip/rk_cru.h
@@ -38,6 +38,7 @@
 #include <dev/clk/rockchip/rk_clk_composite.h>
 #include <dev/clk/rockchip/rk_clk_fract.h>
 #include <dev/clk/rockchip/rk_clk_gate.h>
+#include <dev/clk/rockchip/rk_clk_mmc.h>
 #include <dev/clk/rockchip/rk_clk_mux.h>
 #include <dev/clk/rockchip/rk_clk_pll.h>
 
@@ -120,6 +121,21 @@
 	},								\
 }
 
+/* MMC drive/sample phase clock. */
+#define	MMCPHASE(_id, _name, _pname, _o, _s)				\
+{									\
+	.type = RK_CLK_MMC,						\
+	.clk.mmc = &(struct rk_clk_mmc_def) {				\
+		.clkdef.id = _id,					\
+		.clkdef.name = _name,					\
+		.clkdef.parent_names = (const char *[]){_pname},	\
+		.clkdef.parent_cnt = 1,					\
+		.clkdef.flags = CLK_NODE_STATIC_STRINGS,		\
+		.offset = _o,						\
+		.shift = _s,						\
+	},								\
+}
+
 /* Fractional rate multipier/divider. */
 #define	FRACT(_id, _name, _pname, _f, _o)				\
 {									\
@@ -227,6 +243,7 @@ enum rk_clk_type {
 	RK_CLK_MUX,
 	RK_CLK_ARMCLK,
 	RK_CLK_LINK,
+	RK_CLK_MMC,
 };
 
 struct rk_clk {
@@ -239,6 +256,7 @@ struct rk_clk {
 		struct clk_fixed_def		*fixed;
 		struct rk_clk_fract_def		*fract;
 		struct clk_link_def		*link;
+		struct rk_clk_mmc_def		*mmc;
 	} clk;
 };