diff options
| author | Emmanuel Vadot <manu@FreeBSD.org> | 2026-10-20 08:09:18 +0000 |
|---|---|---|
| committer | Emmanuel Vadot <manu@FreeBSD.org> | 2026-10-30 07:36:46 +0000 |
| commit | e32b082ba28bd3d0e026782eeb93f124ced7508f (patch) | |
| tree | 5a759796a5c262bb67a1eba42f6c800640a0d510 /sys | |
| parent | 2a6580e15b62da11dbe728c6c4f512c15283cd49 (diff) | |
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
Diffstat (limited to 'sys')
| -rw-r--r-- | sys/conf/files.arm64 | 1 | ||||
| -rw-r--r-- | sys/dev/clk/rockchip/rk_clk_mmc.c | 219 | ||||
| -rw-r--r-- | sys/dev/clk/rockchip/rk_clk_mmc.h | 41 | ||||
| -rw-r--r-- | sys/dev/clk/rockchip/rk_cru.c | 3 | ||||
| -rw-r--r-- | sys/dev/clk/rockchip/rk_cru.h | 18 |
5 files changed, 282 insertions, 0 deletions
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; }; |
