git: 5d6d6278979b - main - jh7110: Add StarFive JH7110 clock/reset generator drivers

From: Mitchell Horne <mhorne_at_FreeBSD.org>
Date: Tue, 07 May 2024 16:04:29 UTC
The branch main has been updated by mhorne:

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

commit 5d6d6278979b9eab598a23c804d23e930d2f7268
Author:     Mitchell Horne <mhorne@FreeBSD.org>
AuthorDate: 2024-05-06 17:10:01 +0000
Commit:     Mitchell Horne <mhorne@FreeBSD.org>
CommitDate: 2024-05-07 16:02:57 +0000

    jh7110: Add StarFive JH7110 clock/reset generator drivers
    
    Implement a core clknode driver for the JH7110 (StarFive VisionFive v2)
    platform.
    
    Add clock/reset generator drivers for the PLL, SYS, and AON clock
    groupings.
    
    Co-authored-by: mhorne
    Reviewed by:    mhorne
    Sponsored by:   The FreeBSD Foundation (mhorne's contributions)
    Differential Revision:  https://reviews.freebsd.org/D43037
---
 sys/dev/clk/starfive/jh7110_clk.c     | 277 ++++++++++++++++++++++++
 sys/dev/clk/starfive/jh7110_clk.h     |  72 +++++++
 sys/dev/clk/starfive/jh7110_clk_aon.c | 168 +++++++++++++++
 sys/dev/clk/starfive/jh7110_clk_pll.c | 386 ++++++++++++++++++++++++++++++++++
 sys/dev/clk/starfive/jh7110_clk_pll.h | 211 +++++++++++++++++++
 sys/dev/clk/starfive/jh7110_clk_sys.c | 261 +++++++++++++++++++++++
 sys/riscv/starfive/files.starfive     |   5 +
 7 files changed, 1380 insertions(+)

diff --git a/sys/dev/clk/starfive/jh7110_clk.c b/sys/dev/clk/starfive/jh7110_clk.c
new file mode 100644
index 000000000000..adb5707b3f64
--- /dev/null
+++ b/sys/dev/clk/starfive/jh7110_clk.c
@@ -0,0 +1,277 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Emmanuel Vadot <manu@freebsd.org>
+ * Copyright (c) 2022 Mitchell Horne <mhorne@FreeBSD.org>
+ * Copyright (c) 2024 Jari Sihvola <jsihv@gmx.com>
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+#include <machine/intr.h>
+#include <machine/resource.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+
+#include <dt-bindings/clock/starfive,jh7110-crg.h>
+
+#include <dev/clk/starfive/jh7110_clk.h>
+
+#include "clkdev_if.h"
+#include "hwreset_if.h"
+
+#define	JH7110_DIV_MASK		0xffffff
+#define	JH7110_MUX_SHIFT	24
+#define	JH7110_MUX_MASK		0x3f000000
+#define	JH7110_ENABLE_SHIFT	31
+
+#define	REG_SIZE		4
+
+struct jh7110_clk_sc {
+	uint32_t	offset;
+	uint32_t	flags;
+	uint64_t	d_max;
+	int		id;
+};
+
+#define DIV_ROUND_CLOSEST(n, d)  (((n) + (d) / 2) / (d))
+
+#define	READ4(_sc, _off)				\
+	bus_read_4(_sc->mem_res, _off)
+#define WRITE4(_sc, _off, _val)				\
+	bus_write_4(_sc->mem_res, _off, _val)
+
+#define	DEVICE_LOCK(_clk)				\
+	CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
+#define	DEVICE_UNLOCK(_clk)				\
+	CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
+
+/* Reset functions */
+
+int
+jh7110_reset_assert(device_t dev, intptr_t id, bool assert)
+{
+	struct jh7110_clkgen_softc *sc;
+	uint32_t regvalue, offset, bitmask = 1UL << id % 32;
+
+	sc = device_get_softc(dev);
+	offset = sc->reset_selector_offset + id / 32 * 4;
+
+	mtx_lock(&sc->mtx);
+
+	regvalue = READ4(sc, offset);
+
+	if (assert)
+		regvalue |= bitmask;
+	else
+		regvalue &= ~bitmask;
+	WRITE4(sc, offset, regvalue);
+
+	mtx_unlock(&sc->mtx);
+
+	return (0);
+}
+
+int
+jh7110_reset_is_asserted(device_t dev, intptr_t id, bool *reset)
+{
+	struct jh7110_clkgen_softc *sc;
+	uint32_t regvalue, offset, bitmask;
+
+	sc = device_get_softc(dev);
+	offset = sc->reset_status_offset + id / 32 * 4;
+
+	mtx_lock(&sc->mtx);
+
+	regvalue = READ4(sc, offset);
+	bitmask = 1UL << id % 32;
+
+	mtx_unlock(&sc->mtx);
+
+	*reset = (regvalue & bitmask) == 0;
+
+	return (0);
+}
+
+/* Clock functions */
+
+static int
+jh7110_clk_init(struct clknode *clk, device_t dev)
+{
+	struct jh7110_clkgen_softc *sc;
+	struct jh7110_clk_sc *sc_clk;
+	uint32_t reg;
+	int idx = 0;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	sc_clk = clknode_get_softc(clk);
+
+	if (sc_clk->flags & JH7110_CLK_HAS_MUX) {
+		DEVICE_LOCK(clk);
+		reg = READ4(sc, sc_clk->offset);
+		DEVICE_UNLOCK(clk);
+		idx = (reg & JH7110_MUX_MASK) >> JH7110_MUX_SHIFT;
+	}
+
+	clknode_init_parent_idx(clk, idx);
+
+	return (0);
+}
+
+static int
+jh7110_clk_set_gate(struct clknode *clk, bool enable)
+{
+	struct jh7110_clkgen_softc *sc;
+	struct jh7110_clk_sc *sc_clk;
+	uint32_t reg;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	sc_clk = clknode_get_softc(clk);
+
+	if ((sc_clk->flags & JH7110_CLK_HAS_GATE) == 0)
+		return (0);
+
+	DEVICE_LOCK(clk);
+
+	reg = READ4(sc, sc_clk->offset);
+	if (enable)
+		reg |= (1 << JH7110_ENABLE_SHIFT);
+	else
+		reg &= ~(1 << JH7110_ENABLE_SHIFT);
+	WRITE4(sc, sc_clk->offset, reg);
+
+	DEVICE_UNLOCK(clk);
+
+	return (0);
+}
+
+static int
+jh7110_clk_set_mux(struct clknode *clk, int idx)
+{
+	struct jh7110_clkgen_softc *sc;
+	struct jh7110_clk_sc *sc_clk;
+	uint32_t reg;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	sc_clk = clknode_get_softc(clk);
+
+	if ((sc_clk->flags & JH7110_CLK_HAS_MUX) == 0)
+		return (ENXIO);
+
+	/* Checking index size */
+	if ((idx & (JH7110_MUX_MASK >> JH7110_MUX_SHIFT)) != idx)
+		return (EINVAL);
+
+	DEVICE_LOCK(clk);
+
+	reg = READ4(sc, sc_clk->offset) & ~JH7110_MUX_MASK;
+	reg |= idx << JH7110_MUX_SHIFT;
+	WRITE4(sc, sc_clk->offset, reg);
+
+	DEVICE_UNLOCK(clk);
+
+	return (0);
+}
+
+static int
+jh7110_clk_recalc_freq(struct clknode *clk, uint64_t *freq)
+{
+	struct jh7110_clkgen_softc *sc;
+	struct jh7110_clk_sc *sc_clk;
+	uint32_t divisor;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	sc_clk = clknode_get_softc(clk);
+
+	/* Returning error here causes panic */
+	if ((sc_clk->flags & JH7110_CLK_HAS_DIV) == 0)
+		return (0);
+
+	DEVICE_LOCK(clk);
+
+	divisor = READ4(sc, sc_clk->offset) & JH7110_DIV_MASK;
+
+	DEVICE_UNLOCK(clk);
+
+	if (divisor)
+		*freq = *freq / divisor;
+	else
+		*freq = 0;
+
+	return (0);
+}
+
+static int
+jh7110_clk_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
+    int flags, int *done)
+{
+	struct jh7110_clkgen_softc *sc;
+	struct jh7110_clk_sc *sc_clk;
+	uint32_t divisor;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	sc_clk = clknode_get_softc(clk);
+
+	if ((sc_clk->flags & JH7110_CLK_HAS_DIV) == 0)
+		return (0);
+
+	divisor = MIN(MAX(DIV_ROUND_CLOSEST(fin, *fout), 1UL), sc_clk->d_max);
+
+	if (flags & CLK_SET_DRYRUN)
+		goto done;
+
+	DEVICE_LOCK(clk);
+
+	divisor |= READ4(sc, sc_clk->offset) & ~JH7110_DIV_MASK;
+	WRITE4(sc, sc_clk->offset, divisor);
+
+	DEVICE_UNLOCK(clk);
+
+done:
+	*fout = divisor;
+	*done = 1;
+
+	return (0);
+}
+
+static clknode_method_t jh7110_clknode_methods[] = {
+	/* Device interface */
+	CLKNODEMETHOD(clknode_init,		jh7110_clk_init),
+	CLKNODEMETHOD(clknode_set_gate,		jh7110_clk_set_gate),
+	CLKNODEMETHOD(clknode_set_mux,		jh7110_clk_set_mux),
+	CLKNODEMETHOD(clknode_recalc_freq,	jh7110_clk_recalc_freq),
+	CLKNODEMETHOD(clknode_set_freq,		jh7110_clk_set_freq),
+	CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(jh7110_clknode, jh7110_clknode_class, jh7110_clknode_methods,
+    sizeof(struct jh7110_clk_sc), clknode_class);
+
+int
+jh7110_clk_register(struct clkdom *clkdom, const struct jh7110_clk_def *clkdef)
+{
+	struct clknode *clk;
+	struct jh7110_clk_sc *sc;
+
+	clk = clknode_create(clkdom, &jh7110_clknode_class, &clkdef->clkdef);
+	if (clk == NULL)
+		return (-1);
+
+	sc = clknode_get_softc(clk);
+
+	sc->offset = clkdef->clkdef.id * REG_SIZE;
+
+	sc->flags = clkdef->flags;
+	sc->id = clkdef->clkdef.id;
+	sc->d_max = clkdef->d_max;
+
+	clknode_register(clkdom, clk);
+
+	return (0);
+}
diff --git a/sys/dev/clk/starfive/jh7110_clk.h b/sys/dev/clk/starfive/jh7110_clk.h
new file mode 100644
index 000000000000..882f82032d44
--- /dev/null
+++ b/sys/dev/clk/starfive/jh7110_clk.h
@@ -0,0 +1,72 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Jari Sihvola <jsihv@gmx.com>
+ */
+
+#ifndef _JH7110_CLK_H_
+#define	_JH7110_CLK_H_
+
+#include <dev/clk/clk.h>
+
+#define JH7110_CLK_HAS_GATE	0x01
+#define JH7110_CLK_HAS_MUX	0x02
+#define JH7110_CLK_HAS_DIV	0x04
+#define JH7110_CLK_HAS_INV	0x08
+
+#define AONCRG_RESET_SELECTOR	0x38
+#define AONCRG_RESET_STATUS	0x3c
+#define STGCRG_RESET_SELECTOR	0x74
+#define STGCRG_RESET_STATUS	0x78
+#define SYSCRG_RESET_SELECTOR	0x2f8
+#define SYSCRG_RESET_STATUS	0x308
+
+struct jh7110_clkgen_softc {
+	struct mtx		mtx;
+	struct clkdom		*clkdom;
+	struct resource		*mem_res;
+	uint32_t		reset_status_offset;
+	uint32_t		reset_selector_offset;
+};
+
+struct jh7110_clk_def {
+	struct clknode_init_def clkdef;
+	uint32_t		offset;
+	uint32_t		flags;
+	uint64_t		d_max;
+};
+
+#define	JH7110_CLK(_idx, _name, _pn, _d_max, _flags)		\
+{								\
+	.clkdef.id = _idx,					\
+	.clkdef.name =	_name,					\
+	.clkdef.parent_names = _pn,				\
+	.clkdef.parent_cnt = nitems(_pn),			\
+	.clkdef.flags = CLK_NODE_STATIC_STRINGS,		\
+	.flags = _flags,					\
+	.d_max = _d_max,					\
+}
+
+#define	JH7110_GATE(_idx, _name, _pn)					\
+	JH7110_CLK(_idx, _name, _pn, 0, JH7110_CLK_HAS_GATE)
+#define	JH7110_MUX(_idx, _name, _pn)					\
+	JH7110_CLK(_idx, _name, _pn, 0, JH7110_CLK_HAS_MUX)
+#define	JH7110_DIV(_idx, _name, _pn, _d_max)				\
+	JH7110_CLK(_idx, _name, _pn, _d_max, JH7110_CLK_HAS_DIV)
+#define	JH7110_GATEMUX(_idx, _name, _pn)				\
+	JH7110_CLK(_idx, _name, _pn, 0, JH7110_CLK_HAS_GATE |		\
+	JH7110_CLK_HAS_MUX)
+#define	JH7110_GATEDIV(_idx, _name, _pn, _d_max)			\
+	JH7110_CLK(_idx, _name, _pn, _d_max, JH7110_CLK_HAS_GATE |	\
+	JH7110_CLK_HAS_DIV)
+#define JH7110_INV(_idx, _name, _pn)					\
+	JH7110_CLK(_idx, _name, _pn, 0, JH7110_CLK_HAS_INV)
+
+int jh7110_clk_register(struct clkdom *clkdom,
+    const struct jh7110_clk_def *clkdef);
+int jh7110_ofw_map(struct clkdom *clkdom, uint32_t ncells, phandle_t *cells,
+    struct clknode **clk);
+int jh7110_reset_is_asserted(device_t dev, intptr_t id, bool *reset);
+int jh7110_reset_assert(device_t dev, intptr_t id, bool assert);
+
+#endif	/* _JH7110_CLK_H_ */
diff --git a/sys/dev/clk/starfive/jh7110_clk_aon.c b/sys/dev/clk/starfive/jh7110_clk_aon.c
new file mode 100644
index 000000000000..21b15142835e
--- /dev/null
+++ b/sys/dev/clk/starfive/jh7110_clk_aon.c
@@ -0,0 +1,168 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2016 Michal Meloun <mmel@FreeBSD.org>
+ * Copyright (c) 2020 Oskar Holmlund <oskar.holmlund@ohdata.se>
+ * Copyright (c) 2024 Jari Sihvola <jsihv@gmx.com>
+ */
+
+/* Clocks for JH7110 AON group. PLL driver must be attached before this. */
+
+#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/hwreset/hwreset.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/clk/starfive/jh7110_clk.h>
+
+#include <dt-bindings/clock/starfive,jh7110-crg.h>
+
+#include "clkdev_if.h"
+#include "hwreset_if.h"
+
+static struct ofw_compat_data compat_data[] = {
+	{ "starfive,jh7110-aoncrg",	1 },
+	{ NULL,				0 }
+};
+
+static struct resource_spec res_spec[] = {
+	{ SYS_RES_MEMORY, 0, RF_ACTIVE | RF_SHAREABLE },
+	RESOURCE_SPEC_END
+};
+
+/* parents */
+static const char *gmac0_axi_p[] = { "stg_axiahb" };
+static const char *gmac0_ahb_p[] = { "stg_axiahb" };
+static const char *gmac0_tx_inv_p[] = { "gmac0_tx" };
+static const char *gmac0_tx_p[] = { "gmac0_gtxclk", "gmac0_rmii_rtx" };
+static const char *gmac0_rmii_rtx_p[] = { "gmac0_rmii_refin" };
+
+/* AON clocks */
+static const struct jh7110_clk_def aon_clks[] = {
+	JH7110_GATE(JH7110_AONCLK_GMAC0_AXI, "gmac0_axi", gmac0_axi_p),
+	JH7110_GATE(JH7110_AONCLK_GMAC0_AHB, "gmac0_ahb", gmac0_ahb_p),
+	JH7110_GATEMUX(JH7110_AONCLK_GMAC0_TX, "gmac0_tx", gmac0_tx_p),
+	JH7110_INV(JH7110_AONCLK_GMAC0_TX_INV, "gmac0_tx_inv", gmac0_tx_inv_p),
+	JH7110_DIV(JH7110_AONCLK_GMAC0_RMII_RTX, "gmac0_rmii_rtx",
+	    gmac0_rmii_rtx_p, 30),
+};
+
+static int
+jh7110_clk_aon_probe(device_t dev)
+{
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+
+	if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+		return (ENXIO);
+
+	device_set_desc(dev, "StarFive JH7110 AON clock generator");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+jh7110_clk_aon_attach(device_t dev)
+{
+	struct jh7110_clkgen_softc *sc;
+	int err;
+
+	sc = device_get_softc(dev);
+
+	sc->reset_status_offset = AONCRG_RESET_STATUS;
+	sc->reset_selector_offset = AONCRG_RESET_SELECTOR;
+
+	mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+	err = bus_alloc_resources(dev, res_spec, &sc->mem_res);
+	if (err != 0) {
+		device_printf(dev, "Couldn't allocate resources, error %d\n",
+		    err);
+		return (ENXIO);
+	}
+
+	sc->clkdom = clkdom_create(dev);
+	if (sc->clkdom == NULL) {
+		device_printf(dev, "Couldn't create clkdom, error %d\n", err);
+		return (ENXIO);
+	}
+
+	for (int i = 0; i < nitems(aon_clks); i++) {
+		err = jh7110_clk_register(sc->clkdom, &aon_clks[i]);
+		if (err != 0) {
+			device_printf(dev,
+			    "Couldn't register clk %s, error %d\n",
+				aon_clks[i].clkdef.name, err);
+			return (ENXIO);
+		}
+	}
+
+	if (clkdom_finit(sc->clkdom) != 0)
+		panic("Cannot finalize clkdom initialization\n");
+
+	if (bootverbose)
+		clkdom_dump(sc->clkdom);
+
+	hwreset_register_ofw_provider(dev);
+
+	return (0);
+}
+
+static void
+jh7110_clk_aon_device_lock(device_t dev)
+{
+	struct jh7110_clkgen_softc *sc;
+
+	sc = device_get_softc(dev);
+	mtx_lock(&sc->mtx);
+}
+
+static void
+jh7110_clk_aon_device_unlock(device_t dev)
+{
+	struct jh7110_clkgen_softc *sc;
+
+	sc = device_get_softc(dev);
+	mtx_unlock(&sc->mtx);
+}
+
+static int
+jh7110_clk_aon_detach(device_t dev)
+{
+	/* Detach not supported */
+	return (EBUSY);
+}
+
+static device_method_t jh7110_clk_aon_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,         jh7110_clk_aon_probe),
+	DEVMETHOD(device_attach,	jh7110_clk_aon_attach),
+	DEVMETHOD(device_detach,	jh7110_clk_aon_detach),
+
+	/* clkdev interface */
+	DEVMETHOD(clkdev_device_lock,	jh7110_clk_aon_device_lock),
+	DEVMETHOD(clkdev_device_unlock, jh7110_clk_aon_device_unlock),
+
+	/* Reset interface */
+	DEVMETHOD(hwreset_assert,       jh7110_reset_assert),
+	DEVMETHOD(hwreset_is_asserted,  jh7110_reset_is_asserted),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(jh7110_aon, jh7110_aon_driver, jh7110_clk_aon_methods,
+    sizeof(struct jh7110_clkgen_softc));
+EARLY_DRIVER_MODULE(jh7110_aon, simplebus, jh7110_aon_driver, 0, 0,
+    BUS_PASS_BUS + BUS_PASS_ORDER_LATE);
+MODULE_VERSION(jh7110_aon, 1);
diff --git a/sys/dev/clk/starfive/jh7110_clk_pll.c b/sys/dev/clk/starfive/jh7110_clk_pll.c
new file mode 100644
index 000000000000..5882f33984ae
--- /dev/null
+++ b/sys/dev/clk/starfive/jh7110_clk_pll.c
@@ -0,0 +1,386 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Jari Sihvola <jsihv@gmx.com>
+ * Copyright (c) 2024 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Mitchell Horne
+ * <mhorne@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
+ */
+
+#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 <dev/fdt/simplebus.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/clk/starfive/jh7110_clk.h>
+#include <dev/clk/starfive/jh7110_clk_pll.h>
+#include <dev/syscon/syscon.h>
+
+#include <dt-bindings/clock/starfive,jh7110-crg.h>
+
+#include "clkdev_if.h"
+#include "syscon_if.h"
+
+#define JH7110_SYS_SYSCON_SYSCFG24		0x18
+#define JH7110_SYS_SYSCON_SYSCFG28		0x1c
+#define JH7110_SYS_SYSCON_SYSCFG32		0x20
+#define JH7110_SYS_SYSCON_SYSCFG36		0x24
+#define JH7110_SYS_SYSCON_SYSCFG40		0x28
+#define JH7110_SYS_SYSCON_SYSCFG44		0x2c
+#define JH7110_SYS_SYSCON_SYSCFG48		0x30
+#define JH7110_SYS_SYSCON_SYSCFG52		0x34
+
+#define	DEVICE_LOCK(_clk)				\
+	CLKDEV_DEVICE_LOCK(clknode_get_device(_clk))
+#define	DEVICE_UNLOCK(_clk)				\
+	CLKDEV_DEVICE_UNLOCK(clknode_get_device(_clk))
+
+#define PLL_MASK_FILL(sc, id)					\
+do {								\
+	sc->dacpd_mask = PLL## id ##_DACPD_MASK;		\
+	sc->dsmpd_mask = PLL## id ##_DSMPD_MASK;		\
+	sc->fbdiv_mask = PLL## id ##_FBDIV_MASK;		\
+	sc->frac_mask = PLL## id ##_FRAC_MASK;			\
+	sc->prediv_mask = PLL## id ##_PREDIV_MASK;		\
+	sc->postdiv1_mask = PLL## id ##_POSTDIV1_MASK;		\
+} while (0)
+
+#define PLL_SHIFT_FILL(sc, id)					\
+do {								\
+	sc->dacpd_shift = PLL## id ##_DACPD_SHIFT;		\
+	sc->dsmpd_shift = PLL## id ##_DSMPD_SHIFT;		\
+	sc->fbdiv_shift = PLL## id ##_FBDIV_SHIFT;		\
+	sc->frac_shift = PLL## id ##_FRAC_SHIFT;		\
+	sc->prediv_shift = PLL## id ##_PREDIV_SHIFT;		\
+	sc->postdiv1_shift = PLL## id ##_POSTDIV1_SHIFT;	\
+} while (0)
+
+struct jh7110_clk_pll_softc {
+	struct mtx		mtx;
+	struct clkdom		*clkdom;
+	struct syscon		*syscon;
+};
+
+struct jh7110_pll_clknode_softc {
+	uint32_t	dacpd_offset;
+	uint32_t	dsmpd_offset;
+	uint32_t	fbdiv_offset;
+	uint32_t	frac_offset;
+	uint32_t	prediv_offset;
+	uint32_t	postdiv1_offset;
+
+	uint32_t	dacpd_mask;
+	uint32_t	dsmpd_mask;
+	uint32_t	fbdiv_mask;
+	uint32_t	frac_mask;
+	uint32_t	prediv_mask;
+	uint32_t	postdiv1_mask;
+
+	uint32_t	dacpd_shift;
+	uint32_t	dsmpd_shift;
+	uint32_t	fbdiv_shift;
+	uint32_t	frac_shift;
+	uint32_t	prediv_shift;
+	uint32_t	postdiv1_shift;
+
+	const struct jh7110_pll_syscon_value *syscon_arr;
+	int		syscon_nitems;
+};
+
+static const char *pll_parents[] = { "osc" };
+
+static struct jh7110_clk_def pll_out_clks[] = {
+	{
+		.clkdef.id = JH7110_PLLCLK_PLL0_OUT,
+		.clkdef.name = "pll0_out",
+		.clkdef.parent_names = pll_parents,
+		.clkdef.parent_cnt = nitems(pll_parents),
+		.clkdef.flags = CLK_NODE_STATIC_STRINGS,
+	},
+	{
+		.clkdef.id = JH7110_PLLCLK_PLL1_OUT,
+		.clkdef.name = "pll1_out",
+		.clkdef.parent_names = pll_parents,
+		.clkdef.parent_cnt = nitems(pll_parents),
+		.clkdef.flags = CLK_NODE_STATIC_STRINGS,
+	},
+	{
+		.clkdef.id = JH7110_PLLCLK_PLL2_OUT,
+		.clkdef.name = "pll2_out",
+		.clkdef.parent_names = pll_parents,
+		.clkdef.parent_cnt = nitems(pll_parents),
+		.clkdef.flags = CLK_NODE_STATIC_STRINGS,
+	},
+};
+
+static int jh7110_clk_pll_register(struct clkdom *clkdom,
+    struct jh7110_clk_def *clkdef);
+
+static int
+jh7110_clk_pll_recalc_freq(struct clknode *clk, uint64_t *freq)
+{
+	struct jh7110_clk_pll_softc *sc;
+	struct jh7110_pll_clknode_softc *clk_sc;
+	uint32_t dacpd, dsmpd, fbdiv, prediv, postdiv1;
+	uint64_t frac, fcal = 0;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	clk_sc = clknode_get_softc(clk);
+
+	DEVICE_LOCK(clk);
+
+	dacpd = (SYSCON_READ_4(sc->syscon, clk_sc->dacpd_offset) & clk_sc->dacpd_mask) >>
+	    clk_sc->dacpd_shift;
+	dsmpd = (SYSCON_READ_4(sc->syscon, clk_sc->dsmpd_offset) & clk_sc->dsmpd_mask) >>
+	    clk_sc->dsmpd_shift;
+	fbdiv = (SYSCON_READ_4(sc->syscon, clk_sc->fbdiv_offset) & clk_sc->fbdiv_mask) >>
+	    clk_sc->fbdiv_shift;
+	prediv = (SYSCON_READ_4(sc->syscon, clk_sc->prediv_offset) & clk_sc->prediv_mask) >>
+	    clk_sc->prediv_shift;
+	postdiv1 = (SYSCON_READ_4(sc->syscon, clk_sc->postdiv1_offset) &
+	    clk_sc->postdiv1_mask) >> clk_sc->postdiv1_shift;
+	frac = (SYSCON_READ_4(sc->syscon, clk_sc->frac_offset) & clk_sc->frac_mask) >>
+	    clk_sc->frac_shift;
+
+	DEVICE_UNLOCK(clk);
+
+	/* dacpd and dsmpd both being 0 entails Fraction Multiple Mode */
+	if (dacpd == 0 && dsmpd == 0)
+		fcal = frac * FRAC_PATR_SIZE / (1 << 24);
+
+	*freq = *freq / FRAC_PATR_SIZE * (fbdiv * FRAC_PATR_SIZE + fcal) /
+	    prediv / (1 << postdiv1);
+
+	return (0);
+}
+
+static int
+jh7110_clk_pll_set_freq(struct clknode *clk, uint64_t fin, uint64_t *fout,
+    int flags, int *done)
+{
+	struct jh7110_clk_pll_softc *sc;
+	struct jh7110_pll_clknode_softc *clk_sc;
+	const struct jh7110_pll_syscon_value *syscon_val = NULL;
+
+	sc = device_get_softc(clknode_get_device(clk));
+	clk_sc = clknode_get_softc(clk);
+
+	for (int i = 0; i != clk_sc->syscon_nitems; i++) {
+		if (*fout == clk_sc->syscon_arr[i].freq) {
+			syscon_val = &clk_sc->syscon_arr[i];
+		}
+	}
+
+	if (syscon_val == NULL) {
+		printf("%s: tried to set an unknown frequency %ju for %s\n",
+		       __func__, *fout, clknode_get_name(clk));
+		return (EINVAL);
+	}
+
+	if ((flags & CLK_SET_DRYRUN) != 0) {
+		*done = 1;
+		return (0);
+	}
+
+	DEVICE_LOCK(clk);
+
+	SYSCON_MODIFY_4(sc->syscon, clk_sc->dacpd_offset, clk_sc->dacpd_mask,
+	    syscon_val->dacpd << clk_sc->dacpd_shift & clk_sc->dacpd_mask);
+	SYSCON_MODIFY_4(sc->syscon, clk_sc->dsmpd_offset, clk_sc->dsmpd_mask,
+	    syscon_val->dsmpd << clk_sc->dsmpd_shift & clk_sc->dsmpd_mask);
+	SYSCON_MODIFY_4(sc->syscon, clk_sc->prediv_offset, clk_sc->prediv_mask,
+	    syscon_val->prediv << clk_sc->prediv_shift & clk_sc->prediv_mask);
+	SYSCON_MODIFY_4(sc->syscon, clk_sc->fbdiv_offset, clk_sc->fbdiv_mask,
+	    syscon_val->fbdiv << clk_sc->fbdiv_shift & clk_sc->fbdiv_mask);
+	SYSCON_MODIFY_4(sc->syscon, clk_sc->postdiv1_offset,
+	    clk_sc->postdiv1_mask, (syscon_val->postdiv1 >> 1) <<
+	    clk_sc->postdiv1_shift & clk_sc->postdiv1_mask);
+
+	if (!syscon_val->dacpd && !syscon_val->dsmpd) {
+		SYSCON_MODIFY_4(sc->syscon, clk_sc->frac_offset, clk_sc->frac_mask,
+		    syscon_val->frac << clk_sc->frac_shift & clk_sc->frac_mask);
+	}
+
+	DEVICE_UNLOCK(clk);
+
+	*done = 1;
+	return (0);
+}
+
+static int
+jh7110_clk_pll_init(struct clknode *clk, device_t dev)
+{
+	clknode_init_parent_idx(clk, 0);
+
+	return (0);
+}
+
+static int
+jh7110_clk_pll_probe(device_t dev)
+{
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+
+	if (!ofw_bus_is_compatible(dev, "starfive,jh7110-pll"))
+		return (ENXIO);
+
+	device_set_desc(dev, "StarFive JH7110 PLL clock generator");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+jh7110_clk_pll_attach(device_t dev)
+{
+	struct jh7110_clk_pll_softc *sc;
+	int error;
+
+	sc = device_get_softc(dev);
+
+	mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+	sc->clkdom = clkdom_create(dev);
+	if (sc->clkdom == NULL) {
+		device_printf(dev, "Couldn't create clkdom\n");
+		return (ENXIO);
+	}
+
+	error = syscon_get_by_ofw_node(dev, OF_parent(ofw_bus_get_node(dev)),
+	    &sc->syscon);
+	if (error != 0) {
+		device_printf(dev, "Couldn't get syscon handle of parent\n");
+		return (error);
+	}
+
+	for (int i = 0; i < nitems(pll_out_clks); i++) {
+		error = jh7110_clk_pll_register(sc->clkdom, &pll_out_clks[i]);
+		if (error != 0)
+			device_printf(dev, "Couldn't register clock %s: %d\n",
+			    pll_out_clks[i].clkdef.name, error);
+	}
+
+	error = clkdom_finit(sc->clkdom);
+	if (error != 0) {
+		device_printf(dev, "clkdom_finit() returned %d\n", error);
+	}
+
+	if (bootverbose)
+		clkdom_dump(sc->clkdom);
+
+	return (0);
+}
+
+static void
+jh7110_clk_pll_device_lock(device_t dev)
+{
+	struct jh7110_clk_pll_softc *sc;
+
+	sc = device_get_softc(dev);
+	mtx_lock(&sc->mtx);
+}
+
+static void
+jh7110_clk_pll_device_unlock(device_t dev)
+{
+	struct jh7110_clk_pll_softc *sc;
+
+	sc = device_get_softc(dev);
+	mtx_unlock(&sc->mtx);
+}
+
+static clknode_method_t jh7110_pllnode_methods[] = {
+	/* Device interface */
+	CLKNODEMETHOD(clknode_init,		jh7110_clk_pll_init),
+	CLKNODEMETHOD(clknode_recalc_freq,	jh7110_clk_pll_recalc_freq),
+	CLKNODEMETHOD(clknode_set_freq,		jh7110_clk_pll_set_freq),
+
+	CLKNODEMETHOD_END
+};
+
+static device_method_t jh7110_clk_pll_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,			jh7110_clk_pll_probe),
+	DEVMETHOD(device_attach,		jh7110_clk_pll_attach),
+
+	/* clkdev interface */
+	DEVMETHOD(clkdev_device_lock,		jh7110_clk_pll_device_lock),
+	DEVMETHOD(clkdev_device_unlock,		jh7110_clk_pll_device_unlock),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(jh7110_pllnode, jh7110_pllnode_class, jh7110_pllnode_methods,
+    sizeof(struct jh7110_pll_clknode_softc), clknode_class);
+DEFINE_CLASS_0(jh7110_clk_pll, jh7110_clk_pll_driver, jh7110_clk_pll_methods,
+    sizeof(struct jh7110_clk_pll_softc));
+EARLY_DRIVER_MODULE(jh7110_clk_pll, simplebus, jh7110_clk_pll_driver, 0, 0,
+    BUS_PASS_BUS + BUS_PASS_ORDER_EARLY);
+MODULE_VERSION(jh7110_clk_pll, 1);
+
+int
+jh7110_clk_pll_register(struct clkdom *clkdom, struct jh7110_clk_def *clkdef)
+{
+	struct clknode *clk = NULL;
+	struct jh7110_pll_clknode_softc *sc;
+
+	clk = clknode_create(clkdom, &jh7110_pllnode_class, &clkdef->clkdef);
+	if (clk == NULL)
+		return (1);
+
+	sc = clknode_get_softc(clk);
+
+	switch (clkdef->clkdef.id) {
+	case JH7110_PLLCLK_PLL0_OUT:
+		sc->syscon_arr = jh7110_pll0_syscon_freq;
+		sc->syscon_nitems = nitems(jh7110_pll0_syscon_freq);
+		PLL_MASK_FILL(sc, 0);
+		PLL_SHIFT_FILL(sc, 0);
+		sc->dacpd_offset = JH7110_SYS_SYSCON_SYSCFG24;
+		sc->dsmpd_offset = JH7110_SYS_SYSCON_SYSCFG24;
+		sc->fbdiv_offset = JH7110_SYS_SYSCON_SYSCFG28;
+		sc->frac_offset = JH7110_SYS_SYSCON_SYSCFG32;
+		sc->prediv_offset = JH7110_SYS_SYSCON_SYSCFG36;
+		sc->postdiv1_offset = JH7110_SYS_SYSCON_SYSCFG32;
+		break;
+	case JH7110_PLLCLK_PLL1_OUT:
+		sc->syscon_arr = jh7110_pll1_syscon_freq;
+		sc->syscon_nitems = nitems(jh7110_pll1_syscon_freq);
+		PLL_MASK_FILL(sc, 1);
+		PLL_SHIFT_FILL(sc, 1);
+		sc->dacpd_offset = JH7110_SYS_SYSCON_SYSCFG36;
+		sc->dsmpd_offset = JH7110_SYS_SYSCON_SYSCFG36;
+		sc->fbdiv_offset = JH7110_SYS_SYSCON_SYSCFG36;
+		sc->frac_offset = JH7110_SYS_SYSCON_SYSCFG40;
+		sc->prediv_offset = JH7110_SYS_SYSCON_SYSCFG44;
+		sc->postdiv1_offset = JH7110_SYS_SYSCON_SYSCFG40;
+		break;
+	case JH7110_PLLCLK_PLL2_OUT:
+		sc->syscon_arr = jh7110_pll2_syscon_freq;
+		sc->syscon_nitems = nitems(jh7110_pll2_syscon_freq);
+		PLL_MASK_FILL(sc, 2);
+		PLL_SHIFT_FILL(sc, 2);
+		sc->dacpd_offset = JH7110_SYS_SYSCON_SYSCFG44;
+		sc->dsmpd_offset = JH7110_SYS_SYSCON_SYSCFG44;
+		sc->fbdiv_offset = JH7110_SYS_SYSCON_SYSCFG44;
+		sc->frac_offset = JH7110_SYS_SYSCON_SYSCFG48;
+		sc->prediv_offset = JH7110_SYS_SYSCON_SYSCFG52;
+		sc->postdiv1_offset = JH7110_SYS_SYSCON_SYSCFG48;
+		break;
+	default:
+		return (EINVAL);
+	}
+
+	clknode_register(clkdom, clk);
+
+	return (0);
+}
diff --git a/sys/dev/clk/starfive/jh7110_clk_pll.h b/sys/dev/clk/starfive/jh7110_clk_pll.h
new file mode 100644
index 000000000000..700072a4b465
--- /dev/null
+++ b/sys/dev/clk/starfive/jh7110_clk_pll.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * StarFive JH7110 PLL Clock Generator Driver
+ *
+ * Copyright (C) 2022 Xingyu Wu <xingyu.wu@starfivetech.com>
+ */
+
+#define PLL0_DACPD_SHIFT	24
+#define PLL0_DACPD_MASK		0x1000000
+#define PLL_0_DACPD_SHIFT	24
+#define PLL_0_DACPD_MASK		0x1000000
+
+#define PLL0_DSMPD_SHIFT	25
+#define PLL0_DSMPD_MASK		0x2000000
+#define PLL0_FBDIV_SHIFT	0
+#define PLL0_FBDIV_MASK		0xFFF
+#define PLL0_FRAC_SHIFT		0
+#define PLL0_FRAC_MASK		0xFFFFFF
+#define PLL0_POSTDIV1_SHIFT	28
+#define PLL0_POSTDIV1_MASK	0x30000000
+#define PLL0_PREDIV_SHIFT	0
*** 468 LINES SKIPPED ***