U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] clk: imx: imx8mq: Fix iMX8MQ PLL issue
@ 2026-06-18  5:58 ye.li
  2026-06-20  1:10 ` Peng Fan
  0 siblings, 1 reply; 2+ messages in thread
From: ye.li @ 2026-06-18  5:58 UTC (permalink / raw)
  To: lukma, festevam, u-boot, peng.fan; +Cc: uboot-imx

From: Ye Li <ye.li@nxp.com>

The fractional PLL used on iMX8MQ is not pll14xx, it is different
PLL and not exist in u-boot. Add this fractional PLL driver and
update iMX8MQ clock driver to adapt this fraction PLL.

Signed-off-by: Ye Li <ye.li@nxp.com>
---
 drivers/clk/imx/Makefile       |   2 +-
 drivers/clk/imx/clk-frac-pll.c | 199 +++++++++++++++++++++++++++++++++
 drivers/clk/imx/clk-imx8mq.c   |  55 +++++----
 drivers/clk/imx/clk.h          |   3 +
 4 files changed, 237 insertions(+), 22 deletions(-)
 create mode 100644 drivers/clk/imx/clk-frac-pll.c

diff --git a/drivers/clk/imx/Makefile b/drivers/clk/imx/Makefile
index f2fd6ff8ca0..a825ed9988f 100644
--- a/drivers/clk/imx/Makefile
+++ b/drivers/clk/imx/Makefile
@@ -17,7 +17,7 @@ obj-$(CONFIG_$(PHASE_)CLK_IMX8MN) += clk-imx8mn.o clk-pll14xx.o \
 				clk-composite-8m.o
 obj-$(CONFIG_$(PHASE_)CLK_IMX8MP) += clk-imx8mp.o clk-pll14xx.o \
 				clk-composite-8m.o
-obj-$(CONFIG_$(PHASE_)CLK_IMX8MQ) += clk-imx8mq.o clk-pll14xx.o \
+obj-$(CONFIG_$(PHASE_)CLK_IMX8MQ) += clk-imx8mq.o clk-frac-pll.o \
 				clk-composite-8m.o
 obj-$(CONFIG_$(PHASE_)CLK_IMX93) += clk-imx93.o clk-fracn-gppll.o \
 				clk-gate-93.o clk-composite-93.o
diff --git a/drivers/clk/imx/clk-frac-pll.c b/drivers/clk/imx/clk-frac-pll.c
new file mode 100644
index 00000000000..e286ed56d33
--- /dev/null
+++ b/drivers/clk/imx/clk-frac-pll.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2026 NXP.
+ *
+ */
+
+#include <asm/io.h>
+#include <malloc.h>
+#include <clk-uclass.h>
+#include <dm/device.h>
+#include <dm/devres.h>
+#include <linux/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <clk.h>
+#include <div64.h>
+#include <linux/printk.h>
+#include <linux/bitfield.h>
+
+#include "clk.h"
+
+#define PLL_CFG0		0x0
+#define PLL_CFG1		0x4
+
+#define PLL_LOCK_STATUS		BIT(31)
+#define PLL_PD_MASK		BIT(19)
+#define PLL_BYPASS_MASK		BIT(14)
+#define PLL_NEWDIV_VAL		BIT(12)
+#define PLL_NEWDIV_ACK		BIT(11)
+#define PLL_FRAC_DIV_MASK	GENMASK(30, 7)
+#define PLL_INT_DIV_MASK	GENMASK(6, 0)
+#define PLL_OUTPUT_DIV_MASK	GENMASK(4, 0)
+#define PLL_FRAC_DENOM		0x1000000
+
+#define PLL_FRAC_LOCK_TIMEOUT	10000
+#define PLL_FRAC_ACK_TIMEOUT	500000
+
+struct clk_frac_pll {
+	struct clk			clk;
+	void __iomem			*base;
+};
+
+#define to_clk_frac_pll(_clk) container_of(_clk, struct clk_frac_pll, clk)
+
+static int clk_wait_lock(struct clk_frac_pll *pll)
+{
+	u32 val;
+
+	return readl_poll_timeout(pll->base, val, val & PLL_LOCK_STATUS,
+					PLL_FRAC_LOCK_TIMEOUT);
+}
+
+static int clk_wait_ack(struct clk_frac_pll *pll)
+{
+	u32 val;
+
+	/* return directly if the pll is in powerdown or in bypass */
+	if (readl_relaxed(pll->base) & (PLL_PD_MASK | PLL_BYPASS_MASK))
+		return 0;
+
+	/* Wait for the pll's divfi and divff to be reloaded */
+	return readl_poll_timeout(pll->base, val, val & PLL_NEWDIV_ACK,
+					PLL_FRAC_ACK_TIMEOUT);
+}
+
+static int clk_pll_prepare(struct clk *clk)
+{
+	struct clk_frac_pll *pll = to_clk_frac_pll(clk);
+	u32 val;
+
+	val = readl_relaxed(pll->base + PLL_CFG0);
+	val &= ~PLL_PD_MASK;
+	writel_relaxed(val, pll->base + PLL_CFG0);
+
+	return clk_wait_lock(pll);
+}
+
+static int clk_pll_unprepare(struct clk *clk)
+{
+	struct clk_frac_pll *pll = to_clk_frac_pll(clk);
+	u32 val;
+
+	val = readl_relaxed(pll->base + PLL_CFG0);
+	val |= PLL_PD_MASK;
+	writel_relaxed(val, pll->base + PLL_CFG0);
+
+	return 0;
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk *clk)
+{
+	struct clk_frac_pll *pll = to_clk_frac_pll(clk);
+	u32 val, divff, divfi, divq;
+	ulong parent_rate = clk_get_parent_rate(clk);
+	u64 temp64 = parent_rate;
+	u64 rate;
+
+	val = readl_relaxed(pll->base + PLL_CFG0);
+	divq = (FIELD_GET(PLL_OUTPUT_DIV_MASK, val) + 1) * 2;
+	val = readl_relaxed(pll->base + PLL_CFG1);
+	divff = FIELD_GET(PLL_FRAC_DIV_MASK, val);
+	divfi = FIELD_GET(PLL_INT_DIV_MASK, val);
+
+	temp64 *= 8;
+	temp64 *= divff;
+	do_div(temp64, PLL_FRAC_DENOM);
+	do_div(temp64, divq);
+
+	rate = parent_rate * 8 * (divfi + 1);
+	do_div(rate, divq);
+	rate += temp64;
+
+	return rate;
+}
+
+static ulong clk_pll_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_frac_pll *pll = to_clk_frac_pll(clk);
+	ulong parent_rate = clk_get_parent_rate(clk);
+	u32 val, divfi, divff;
+	u64 temp64;
+	int ret;
+
+	parent_rate *= 8;
+	rate *= 2;
+	divfi = rate / parent_rate;
+	temp64 = parent_rate * divfi;
+	temp64 = rate - temp64;
+	temp64 *= PLL_FRAC_DENOM;
+	do_div(temp64, parent_rate);
+	divff = temp64;
+
+	val = readl_relaxed(pll->base + PLL_CFG1);
+	val &= ~(PLL_FRAC_DIV_MASK | PLL_INT_DIV_MASK);
+	val |= (divff << 7) | (divfi - 1);
+	writel_relaxed(val, pll->base + PLL_CFG1);
+
+	val = readl_relaxed(pll->base + PLL_CFG0);
+	val &= ~0x1f;
+	writel_relaxed(val, pll->base + PLL_CFG0);
+
+	/* Set the NEV_DIV_VAL to reload the DIVFI and DIVFF */
+	val = readl_relaxed(pll->base + PLL_CFG0);
+	val |= PLL_NEWDIV_VAL;
+	writel_relaxed(val, pll->base + PLL_CFG0);
+
+	ret = clk_wait_ack(pll);
+
+	/* clear the NEV_DIV_VAL */
+	val = readl_relaxed(pll->base + PLL_CFG0);
+	val &= ~PLL_NEWDIV_VAL;
+	writel_relaxed(val, pll->base + PLL_CFG0);
+
+	if (ret)
+		return ret;
+
+	return clk_pll_recalc_rate(clk);
+}
+
+static const struct clk_ops clk_frac_pll_ops = {
+	.enable		= clk_pll_prepare,
+	.disable	= clk_pll_unprepare,
+	.set_rate	= clk_pll_set_rate,
+	.get_rate	= clk_pll_recalc_rate,
+};
+
+struct clk *imx_clk_frac_pll(const char *name, const char *parent_name,
+			     void __iomem *base)
+{
+	struct clk_frac_pll *pll;
+	struct clk *clk;
+	int ret;
+
+	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return ERR_PTR(-ENOMEM);
+
+	pll->base = base;
+	clk = &pll->clk;
+
+	ret = clk_register(clk, "imx_clk_frac_pll", name, parent_name);
+	if (ret) {
+		pr_err("%s: failed to register pll %s %d\n",
+		       __func__, name, ret);
+		kfree(pll);
+		return ERR_PTR(ret);
+	}
+
+	return clk;
+}
+
+U_BOOT_DRIVER(clk_frac_pll) = {
+	.name	= "imx_clk_frac_pll",
+	.id	= UCLASS_CLK,
+	.ops	= &clk_frac_pll_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/clk/imx/clk-imx8mq.c b/drivers/clk/imx/clk-imx8mq.c
index fe6cba19758..78156003ad9 100644
--- a/drivers/clk/imx/clk-imx8mq.c
+++ b/drivers/clk/imx/clk-imx8mq.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Copyright 2019 NXP
+ * Copyright 2019, 2026 NXP
  * Copyright 2022 Purism
  * Peng Fan <peng.fan@nxp.com>
  */
@@ -155,39 +155,52 @@ static int imx8mq_clk_probe(struct udevice *dev)
 	       imx_clk_mux(dev, "dram_pll_ref_sel", base + 0x60, 0, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_ARM_PLL_REF_SEL,
-	       imx_clk_mux(dev, "arm_pll_ref_sel", base + 0x28, 0, 2,
+	       imx_clk_mux(dev, "arm_pll_ref_sel", base + 0x28, 16, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_GPU_PLL_REF_SEL,
-	       imx_clk_mux(dev, "gpu_pll_ref_sel", base + 0x18, 0, 2,
+	       imx_clk_mux(dev, "gpu_pll_ref_sel", base + 0x18, 16, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_VPU_PLL_REF_SEL,
-	       imx_clk_mux(dev, "vpu_pll_ref_sel", base + 0x20, 0, 2,
+	       imx_clk_mux(dev, "vpu_pll_ref_sel", base + 0x20, 16, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_SYS3_PLL1_REF_SEL,
 	       imx_clk_mux(dev, "sys3_pll_ref_sel", base + 0x48, 0, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_AUDIO_PLL1_REF_SEL,
-	       imx_clk_mux(dev, "audio_pll1_ref_sel", base + 0x0, 0, 2,
+	       imx_clk_mux(dev, "audio_pll1_ref_sel", base + 0x0, 16, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_AUDIO_PLL2_REF_SEL,
-	       imx_clk_mux(dev, "audio_pll2_ref_sel", base + 0x8, 0, 2,
+	       imx_clk_mux(dev, "audio_pll2_ref_sel", base + 0x8, 16, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_VIDEO_PLL1_REF_SEL,
-	       imx_clk_mux(dev, "video_pll1_ref_sel", base + 0x10, 0, 2,
+	       imx_clk_mux(dev, "video_pll1_ref_sel", base + 0x10, 16, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 	clk_dm(IMX8MQ_VIDEO2_PLL1_REF_SEL,
 	       imx_clk_mux(dev, "video_pll2_ref_sel", base + 0x54, 0, 2,
 			   pll_ref_sels, ARRAY_SIZE(pll_ref_sels)));
 
+	clk_dm(IMX8MQ_ARM_PLL_REF_DIV,
+	       imx_clk_divider(dev, "arm_pll_ref_div", "arm_pll_ref_sel", base + 0x28, 5, 6));
+	clk_dm(IMX8MQ_GPU_PLL_REF_DIV,
+	       imx_clk_divider(dev, "gpu_pll_ref_div", "gpu_pll_ref_sel", base + 0x18, 5, 6));
+	clk_dm(IMX8MQ_VPU_PLL_REF_DIV,
+	       imx_clk_divider(dev, "vpu_pll_ref_div", "vpu_pll_ref_sel", base + 0x20, 5, 6));
+	clk_dm(IMX8MQ_AUDIO_PLL1_REF_DIV,
+	       imx_clk_divider(dev, "audio_pll1_ref_div", "audio_pll1_ref_sel", base + 0x0, 5, 6));
+	clk_dm(IMX8MQ_AUDIO_PLL2_REF_DIV,
+	       imx_clk_divider(dev, "audio_pll2_ref_div", "audio_pll2_ref_sel", base + 0x8, 5, 6));
+	clk_dm(IMX8MQ_VIDEO_PLL1_REF_DIV,
+	       imx_clk_divider(dev, "video_pll1_ref_div", "video_pll1_ref_sel", base + 0x10, 5, 6));
+
 	clk_dm(IMX8MQ_ARM_PLL,
-	       imx_clk_pll14xx("arm_pll", "arm_pll_ref_sel",
-			       base + 0x28, &imx_1416x_pll));
+	       imx_clk_frac_pll("arm_pll", "arm_pll_ref_div",
+				base + 0x28));
 	clk_dm(IMX8MQ_GPU_PLL,
-	       imx_clk_pll14xx("gpu_pll", "gpu_pll_ref_sel",
-			       base + 0x18, &imx_1416x_pll));
+	       imx_clk_frac_pll("gpu_pll", "gpu_pll_ref_div",
+				base + 0x18));
 	clk_dm(IMX8MQ_VPU_PLL,
-	       imx_clk_pll14xx("vpu_pll", "vpu_pll_ref_sel",
-			       base + 0x20, &imx_1416x_pll));
+	       imx_clk_frac_pll("vpu_pll", "vpu_pll_ref_div",
+				base + 0x20));
 
 	clk_dm(IMX8MQ_SYS1_PLL1,
 	       clk_register_fixed_rate(NULL, "sys1_pll", 800000000));
@@ -196,14 +209,14 @@ static int imx8mq_clk_probe(struct udevice *dev)
 	clk_dm(IMX8MQ_SYS2_PLL1,
 	       clk_register_fixed_rate(NULL, "sys3_pll", 1000000000));
 	clk_dm(IMX8MQ_AUDIO_PLL1,
-	       imx_clk_pll14xx("audio_pll1", "audio_pll1_ref_sel",
-			       base + 0x0, &imx_1443x_pll));
+	       imx_clk_frac_pll("audio_pll1", "audio_pll1_ref_div",
+				base + 0x0));
 	clk_dm(IMX8MQ_AUDIO_PLL2,
-	       imx_clk_pll14xx("audio_pll2", "audio_pll2_ref_sel",
-			       base + 0x8, &imx_1443x_pll));
+	       imx_clk_frac_pll("audio_pll2", "audio_pll2_ref_div",
+				base + 0x8));
 	clk_dm(IMX8MQ_VIDEO_PLL1,
-	       imx_clk_pll14xx("video_pll1", "video_pll1_ref_sel",
-			       base + 0x10, &imx_1443x_pll));
+	       imx_clk_frac_pll("video_pll1", "video_pll1_ref_div",
+				base + 0x10));
 
 	/* PLL bypass out */
 	clk_dm(IMX8MQ_ARM_PLL_BYPASS,
@@ -356,8 +369,8 @@ static int imx8mq_clk_probe(struct udevice *dev)
 	clk_dm(IMX8MQ_CLK_A53_DIV,
 	       imx_clk_divider2(dev, "arm_a53_div", "arm_a53_cg",
 				base + 0x8000, 0, 3));
-	clk_dm(IMX8MQ_CLK_A53_CORE,
-	       imx_clk_mux2(dev, "arm_a53_src", base + 0x9880, 24, 1,
+	clk_dm(IMX8MQ_CLK_ARM,
+	       imx_clk_mux2(dev, "arm_a53_core", base + 0x9880, 24, 1,
 			    imx8mq_a53_core_sels, ARRAY_SIZE(imx8mq_a53_core_sels)));
 
 	clk_dm(IMX8MQ_CLK_AHB,
diff --git a/drivers/clk/imx/clk.h b/drivers/clk/imx/clk.h
index b53f35df84f..6aef72c0212 100644
--- a/drivers/clk/imx/clk.h
+++ b/drivers/clk/imx/clk.h
@@ -78,6 +78,9 @@ struct clk *imx_clk_pll14xx(const char *name, const char *parent_name,
 			    void __iomem *base,
 			    const struct imx_pll14xx_clk *pll_clk);
 
+struct clk *imx_clk_frac_pll(const char *name, const char *parent_name,
+			     void __iomem *base);
+
 struct clk *clk_register_gate2(struct udevice *dev, const char *name,
 		const char *parent_name, unsigned long flags,
 		void __iomem *reg, u8 bit_idx, u8 cgr_val,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] clk: imx: imx8mq: Fix iMX8MQ PLL issue
  2026-06-18  5:58 [PATCH] clk: imx: imx8mq: Fix iMX8MQ PLL issue ye.li
@ 2026-06-20  1:10 ` Peng Fan
  0 siblings, 0 replies; 2+ messages in thread
From: Peng Fan @ 2026-06-20  1:10 UTC (permalink / raw)
  To: ye.li; +Cc: lukma, festevam, u-boot, peng.fan, uboot-imx

On Thu, Jun 18, 2026 at 01:58:56PM +0800, ye.li@oss.nxp.com wrote:
>From: Ye Li <ye.li@nxp.com>
>
>The fractional PLL used on iMX8MQ is not pll14xx, it is different
>PLL and not exist in u-boot. Add this fractional PLL driver and
>update iMX8MQ clock driver to adapt this fraction PLL.
>

Better add a fixes tag.

>Signed-off-by: Ye Li <ye.li@nxp.com>
>---
> drivers/clk/imx/Makefile       |   2 +-
...
>@@ -0,0 +1,199 @@
>+// SPDX-License-Identifier: GPL-2.0
>+/*
>+ * Copyright 2026 NXP.

Nit: Copyright 2026 NXP

Otherwise:

Reviewed-by: Peng Fan <peng.fan@nxp.com>

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-06-20  1:07 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-18  5:58 [PATCH] clk: imx: imx8mq: Fix iMX8MQ PLL issue ye.li
2026-06-20  1:10 ` Peng Fan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox