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
This commit is contained in:
Emmanuel Vadot committed 2026-10-30 08:36:46 +01:00
1 parent 2a6580e15b
commit e32b082ba2
5 files changed
+282

No files matched your search

+1
View File
@@ -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
+219
View File
@@ -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);
}
+41
View File
@@ -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_ */
+3
View File
@@ -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);
+18
View File
@@ -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;
};