mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] clk / clocksource: Add Ingenic X1600 SoC support
@ 2026-10-25 19:18 H. Nikolaus Schaller
  2026-10-25 19:18 ` [PATCH 1/4] dt-bindings: clock: Add Ingenic X1600 CGU bindings H. Nikolaus Schaller
                   ` (3 more replies)
  0 siblings, 4 replies; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-25 19:18 UTC (permalink / raw)
  To: Stephen Boyd, Brian Masney, Jerome Brunet, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Paul Cercueil
  Cc: linux-clk, devicetree, linux-kernel, linux-mips, letux-kernel,
	kernel, H. Nikolaus Schaller

PATCH V1 2026-10-25 20:18:26:
This patch series introduces hardware support for the Clock Generation Unit
(CGU) and the Timer Clock Unit (TCU) found in the Ingenic X1600 SoC.
To achieve this cleanly, the common Ingenic clock and timer infrastructure
is modernized and parameterized.

As a side effect of making the core structures flexible enough to handle
the architectural layout of the X1600, it became straightforward to fully
integrate support for the legacy JZ4730 SoC later.

The series is structured as follows:

Patch 1: Adds the device tree bindings and clock definitions for the X1600,
         incorporating the specific I2S handling, routing, and RX/TX gates.
         
Patch 2: Implements the Ingenic X1600 CGU clock driver. It updates the common
         CGU core framework to support secondary post-VCO dividers (OD1),
         and bypasses the lack of compound divider support via explicit
         mdiv/nddiv handling for the I2S clocks.

Patch 3&4: Adapt the existing JZ4760 and X1000 CGU drivers to match the expanded
         signature of the calc_m_n_od() callback from Patch 2, preventing
         compilation breakages.

The changes have been successfully tested on CI20 (JZ4780), LX16 (X1600).
And on LX20 (X2000), LX26 (X2600) boards (with additional, specific patches).


H. Nikolaus Schaller (3):
  dt-bindings: clock: Add Ingenic X1600 CGU bindings
  clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework
    changes
  clk: ingenic: x1000-cgu: Adapt calc_m_n_od() to core framework changes

Paul Boddie (1):
  clk: ingenic: Add Ingenic X1600 CGU clock driver support

 .../bindings/clock/ingenic,cgu.yaml           |   2 +
 drivers/clk/ingenic/Kconfig                   |  10 +
 drivers/clk/ingenic/Makefile                  |   1 +
 drivers/clk/ingenic/cgu.c                     | 150 ++++--
 drivers/clk/ingenic/cgu.h                     |  42 +-
 drivers/clk/ingenic/jz4760-cgu.c              |   3 +-
 drivers/clk/ingenic/x1000-cgu.c               |   3 +-
 drivers/clk/ingenic/x1600-cgu.c               | 476 ++++++++++++++++++
 include/dt-bindings/clock/ingenic,x1600-cgu.h |  64 +++
 9 files changed, 716 insertions(+), 35 deletions(-)
 create mode 100644 drivers/clk/ingenic/x1600-cgu.c
 create mode 100644 include/dt-bindings/clock/ingenic,x1600-cgu.h

-- 
2.56.0


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

* [PATCH 1/4] dt-bindings: clock: Add Ingenic X1600 CGU bindings
  2026-10-25 19:18 [PATCH 0/4] clk / clocksource: Add Ingenic X1600 SoC support H. Nikolaus Schaller
@ 2026-10-25 19:18 ` H. Nikolaus Schaller
  2026-10-25 19:18 ` [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support H. Nikolaus Schaller
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-25 19:18 UTC (permalink / raw)
  To: Stephen Boyd, Brian Masney, Jerome Brunet, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Paul Cercueil
  Cc: linux-clk, devicetree, linux-kernel, linux-mips, letux-kernel,
	kernel, H. Nikolaus Schaller, Paul Boddie

Add the device tree bindings and the clock ID definitions for the
Clock Generation Unit (CGU) of the Ingenic X1600 SoC.

The clock layout features specific I2S handling. To properly support
the I2S controllers on the X1600 without relying on complex compound
dividers that are not yet natively supported by the drivers, split
the structures to explicitly describe the additional I2S divider
fields. This design accounts for the current driver limitations
regarding division encoding while matching the hardware's internal
gating architecture.

Furthermore, introduce dedicated I2S0 RX and TX clock definitions to
allow independent and precise routing for I2S stream usage.

Co-developed-by: Paul Boddie <paul@boddie.org.uk>
Signed-off-by: Paul Boddie <paul@boddie.org.uk>
Co-developed-by: H. Nikolaus Schaller <hns@goldelico.com>
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 .../bindings/clock/ingenic,cgu.yaml           |  2 +
 include/dt-bindings/clock/ingenic,x1600-cgu.h | 64 +++++++++++++++++++
 2 files changed, 66 insertions(+)
 create mode 100644 include/dt-bindings/clock/ingenic,x1600-cgu.h

diff --git a/Documentation/devicetree/bindings/clock/ingenic,cgu.yaml b/Documentation/devicetree/bindings/clock/ingenic,cgu.yaml
index 509df06b9c9d8..c622131c5c49b 100644
--- a/Documentation/devicetree/bindings/clock/ingenic,cgu.yaml
+++ b/Documentation/devicetree/bindings/clock/ingenic,cgu.yaml
@@ -28,6 +28,7 @@ select:
           - ingenic,jz4770-cgu
           - ingenic,jz4780-cgu
           - ingenic,x1000-cgu
+          - ingenic,x1600-cgu
           - ingenic,x1830-cgu
   required:
     - compatible
@@ -58,6 +59,7 @@ properties:
           - ingenic,jz4770-cgu
           - ingenic,jz4780-cgu
           - ingenic,x1000-cgu
+          - ingenic,x1600-cgu
           - ingenic,x1830-cgu
       - const: simple-mfd
     minItems: 1
diff --git a/include/dt-bindings/clock/ingenic,x1600-cgu.h b/include/dt-bindings/clock/ingenic,x1600-cgu.h
new file mode 100644
index 0000000000000..a0001a63a6b54
--- /dev/null
+++ b/include/dt-bindings/clock/ingenic,x1600-cgu.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause */
+/*
+ * This header provides clock numbers for the ingenic,x1600-cgu DT binding.
+ *
+ * They are roughly ordered as:
+ *   - external clocks
+ *   - PLLs
+ *   - muxes/dividers in the order they appear in the x1600 programmers manual
+ *   - gates in order of their bit in the CLKGR* registers
+ */
+
+#ifndef __DT_BINDINGS_CLOCK_X1600_CGU_H__
+#define __DT_BINDINGS_CLOCK_X1600_CGU_H__
+
+/*
+ * beware of unwritten rules:
+ * - list must start with 0
+ * - list must not have "holes"
+ * - all clock parents must have lower number than a clock itself so that
+ *   parents are initialized before
+ */
+
+#define X1600_CLK_EXCLK			0
+#define X1600_CLK_RTCLK			1
+#define X1600_CLK_12M			2
+#define X1600_CLK_APLL			3
+#define X1600_CLK_EPLL			4
+#define X1600_CLK_MPLL			5
+#define X1600_CLK_SCLKA			6
+#define X1600_CLK_CPUMUX		7
+#define X1600_CLK_CPU			8
+#define X1600_CLK_L2CACHE		9
+#define X1600_CLK_AHB0			10
+#define X1600_CLK_AHB2PMUX		11
+#define X1600_CLK_AHB2			12
+#define X1600_CLK_PCLK			13
+#define X1600_CLK_DDR			14
+#define X1600_CLK_I2S0			15
+#define X1600_CLK_I2S1			16
+#define X1600_CLK_LCDPIXCLK		17
+#define X1600_CLK_MAC			18
+#define X1600_CLK_MSC0			19
+#define X1600_CLK_MSC1			20
+#define X1600_CLK_SSI0			21
+#define X1600_CLK_CIMMCLK		22
+#define X1600_CLK_EXCLK_DIV512		23
+#define X1600_CLK_RTC			24
+#define X1600_CLK_OTGPHY		25
+#define X1600_CLK_NEMC			26
+#define X1600_CLK_OTG0			27
+#define X1600_CLK_SMB0			28
+#define X1600_CLK_SMB1			29
+#define X1600_CLK_AIC			30
+#define X1600_CLK_SADC			31
+#define X1600_CLK_UART0			32
+#define X1600_CLK_UART1			33
+#define X1600_CLK_UART2			34
+#define X1600_CLK_UART3			35
+#define X1600_CLK_PDMA			36
+#define X1600_CLK_AES			37
+#define X1600_CLK_I2S0_RX		38
+#define X1600_CLK_I2S0_TX		39
+
+#endif /* __DT_BINDINGS_CLOCK_X1600_CGU_H__ */
-- 
2.56.0


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

* [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support
  2026-10-25 19:18 [PATCH 0/4] clk / clocksource: Add Ingenic X1600 SoC support H. Nikolaus Schaller
  2026-10-25 19:18 ` [PATCH 1/4] dt-bindings: clock: Add Ingenic X1600 CGU bindings H. Nikolaus Schaller
@ 2026-10-25 19:18 ` H. Nikolaus Schaller
  2026-10-26 10:16   ` kernel test robot
  2026-10-25 19:18 ` [PATCH 3/4] clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework changes H. Nikolaus Schaller
  2026-10-25 19:18 ` [PATCH 4/4] clk: ingenic: x1000-cgu: " H. Nikolaus Schaller
  3 siblings, 1 reply; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-25 19:18 UTC (permalink / raw)
  To: Stephen Boyd, Brian Masney, Jerome Brunet, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Paul Cercueil
  Cc: linux-clk, devicetree, linux-kernel, linux-mips, letux-kernel,
	kernel, Paul Boddie, H. Nikolaus Schaller

From: Paul Boddie <paul@boddie.org.uk>

Introduce clock driver support for the Clock Generation Unit (CGU)
found in the Ingenic X1600 SoC.

To support the X1600 platform, update the common Ingenic CGU core
framework to accommodate a secondary post-VCO divider (OD1) within
the PLL configuration registers. Additionally, prevent a NULL pointer
dereference in the rate determination path by properly supplying the
secondary divider tracking during PLL calculation calls.

Implement custom handling for the I2S clocks on the X1600. Since
complex compound hardware dividers are not yet natively supported by the
core framework, explicit M-divider (mdiv) and N/D-divider (nddiv)
structures are introduced to describe these specialized fields. This
avoids a compilation failure and enables a stable, though limited,
division encoding suitable for current needs.

Furthermore, this patch:
- Fixes the gating of the I2S0 and I2S1 clocks, matching the control
  bit behavior which functions as a clock enable flag.
- Introduces the custom OTG PHY clock operations and initialisation
  routines utilizing the hardware's OPCR registers.
- Registers the independent I2S0 RX and TX gate clocks employed by the
  X1600 peripheral block.

Tested successfully on the LX16 (X1600) boards and CI20 (JZ4780) to
not interefere.

Checkpatch complains about not 4 lines of help paragraph but the
style is the same as for the other machines. It also recommends
using 'unsigned int' for the new struct ingenic_cgu_mdiv_info
and struct ingenic_cgu_nddiv_info but this follows the style 'unsigned'
of all other structs in cgu.h. Finally it complains about the empty
line between struct ingenic_cgu_clk_info x1600_cgu_clocks[] = {
and the comment. But I think it is more readable this way.

Co-developed-by: H. Nikolaus Schaller <hns@goldelico.com>
Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
Co-developed-by: Paul Boddie <paul@boddie.org.uk>
Signed-off-by: Paul Boddie <paul@boddie.org.uk>
---
 drivers/clk/ingenic/Kconfig     |  10 +
 drivers/clk/ingenic/Makefile    |   1 +
 drivers/clk/ingenic/cgu.c       | 150 +++++++---
 drivers/clk/ingenic/cgu.h       |  42 ++-
 drivers/clk/ingenic/x1600-cgu.c | 476 ++++++++++++++++++++++++++++++++
 5 files changed, 646 insertions(+), 33 deletions(-)
 create mode 100644 drivers/clk/ingenic/x1600-cgu.c

diff --git a/drivers/clk/ingenic/Kconfig b/drivers/clk/ingenic/Kconfig
index f80ac4f2992b4..9b8b00c9e6d13 100644
--- a/drivers/clk/ingenic/Kconfig
+++ b/drivers/clk/ingenic/Kconfig
@@ -75,6 +75,16 @@ config INGENIC_CGU_X1000
 
 	  If building for a X1000 SoC, you want to say Y here.
 
+config INGENIC_CGU_X1600
+	bool "Ingenic X1600 CGU driver"
+	default MACH_X1600
+	select INGENIC_CGU_COMMON
+	help
+	  Support the clocks provided by the CGU hardware on Ingenic X1600
+	  and compatible SoCs.
+
+	  If building for a X1600 SoC, you want to say Y here.
+
 config INGENIC_CGU_X1830
 	bool "Ingenic X1830 CGU driver"
 	default MACH_X1830
diff --git a/drivers/clk/ingenic/Makefile b/drivers/clk/ingenic/Makefile
index 81d8e23c26365..7e93e0893abe2 100644
--- a/drivers/clk/ingenic/Makefile
+++ b/drivers/clk/ingenic/Makefile
@@ -7,5 +7,6 @@ obj-$(CONFIG_INGENIC_CGU_JZ4760)	+= jz4760-cgu.o
 obj-$(CONFIG_INGENIC_CGU_JZ4770)	+= jz4770-cgu.o
 obj-$(CONFIG_INGENIC_CGU_JZ4780)	+= jz4780-cgu.o
 obj-$(CONFIG_INGENIC_CGU_X1000)		+= x1000-cgu.o
+obj-$(CONFIG_INGENIC_CGU_X1600)		+= x1600-cgu.o
 obj-$(CONFIG_INGENIC_CGU_X1830)		+= x1830-cgu.o
 obj-$(CONFIG_INGENIC_TCU_CLK)		+= tcu.o
diff --git a/drivers/clk/ingenic/cgu.c b/drivers/clk/ingenic/cgu.c
index 41e4c69131bd6..b4ef8ddf043a7 100644
--- a/drivers/clk/ingenic/cgu.c
+++ b/drivers/clk/ingenic/cgu.c
@@ -4,6 +4,7 @@
  *
  * Copyright (c) 2013-2015 Imagination Technologies
  * Author: Paul Burton <paul.burton@mips.com>
+ * Copyright (c) 2023, 2024 Paul Boddie <paul@boddie.org.uk>
  */
 
 #include <linux/bitops.h>
@@ -76,6 +77,40 @@ ingenic_cgu_gate_set(struct ingenic_cgu *cgu,
  * PLL operations
  */
 
+static unsigned
+ingenic_pll_recalc_rate_od(u32 ctl, u8 od_shift, u8 od_bits, u8 od_max,
+			   const s8 *od_encoding)
+{
+	unsigned int od, od_enc = 0;
+
+	if (od_bits > 0) {
+		od_enc = ctl >> od_shift;
+		od_enc &= GENMASK(od_bits - 1, 0);
+	}
+
+	/*
+	 * Use the encoding table if indicated. Otherwise, use the value
+	 * directly, interpreting an encoded value of zero as a divider
+	 * value of one.
+	 */
+	if (od_encoding) {
+		for (od = 0; od < od_max; od++)
+			if (od_encoding[od] == od_enc)
+				break;
+		od++;
+	} else {
+		od = od_enc ? od_enc : 1;
+	}
+
+	/* if od_max = 0, od_bits should be 0 and od is fixed to 1. */
+	if (od_max == 0)
+		WARN_ON_ONCE(od_bits != 0);
+	else
+		WARN_ON_ONCE(od == od_max);
+
+	return od;
+}
+
 static unsigned long
 ingenic_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
 {
@@ -83,7 +118,7 @@ ingenic_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
 	const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
 	struct ingenic_cgu *cgu = ingenic_clk->cgu;
 	const struct ingenic_cgu_pll_info *pll_info;
-	unsigned m, n, od, od_enc = 0;
+	unsigned int m, n, od, od1;
 	bool bypass;
 	u32 ctl;
 
@@ -97,71 +132,82 @@ ingenic_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
 	n = (ctl >> pll_info->n_shift) & GENMASK(pll_info->n_bits - 1, 0);
 	n += pll_info->n_offset;
 
-	if (pll_info->od_bits > 0) {
-		od_enc = ctl >> pll_info->od_shift;
-		od_enc &= GENMASK(pll_info->od_bits - 1, 0);
-	}
-
 	if (pll_info->bypass_bit >= 0) {
-		ctl = readl(cgu->base + pll_info->bypass_reg);
+		u32 ctl2 = readl(cgu->base + pll_info->bypass_reg);
 
-		bypass = !!(ctl & BIT(pll_info->bypass_bit));
+		bypass = !!(ctl2 & BIT(pll_info->bypass_bit));
 
 		if (bypass)
 			return parent_rate;
 	}
 
-	for (od = 0; od < pll_info->od_max; od++)
-		if (pll_info->od_encoding[od] == od_enc)
-			break;
+	od = ingenic_pll_recalc_rate_od(ctl, pll_info->od_shift, pll_info->od_bits,
+					pll_info->od_max, pll_info->od_encoding);
 
-	/* if od_max = 0, od_bits should be 0 and od is fixed to 1. */
-	if (pll_info->od_max == 0)
-		BUG_ON(pll_info->od_bits != 0);
-	else
-		BUG_ON(od == pll_info->od_max);
-	od++;
+	od1 = ingenic_pll_recalc_rate_od(ctl, pll_info->od1_shift, pll_info->od1_bits,
+					 pll_info->od1_max, pll_info->od_encoding);
 
 	return div_u64((u64)parent_rate * m * pll_info->rate_multiplier,
-		n * od);
+		n * od * od1);
 }
 
 static void
 ingenic_pll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
 			unsigned long rate, unsigned long parent_rate,
-			unsigned int *pm, unsigned int *pn, unsigned int *pod)
+			unsigned int *pm, unsigned int *pn, unsigned int *pod,
+			unsigned int *pod1)
 {
-	unsigned int m, n, od = 1;
+	unsigned int m, n, od = 1, od1 = 1;
 
 	/*
-	 * The frequency after the input divider must be between 10 and 50 MHz.
+	 * The frequency after the input divider must be within the range
+	 * defined in the programming manual as FREF:
+	 *
+	 * JZ4740: 1 MHz - 15 MHz
+	 * JZ4780: 183 kHz - 1.5 GHz
+	 * X1000:  10 MHz - 50 MHz
+	 * X1600:  1 MHz - 800 MHz
+	 * X2000:  5 MHz - 200 MHz
+	 *
 	 * The highest divider yields the best resolution.
 	 */
 	n = parent_rate / (10 * MHZ);
 	n = min_t(unsigned int, n, 1 << pll_info->n_bits);
 	n = max_t(unsigned int, n, pll_info->n_offset);
 
-	m = (rate / MHZ) * od * n / (parent_rate / MHZ);
+	/*
+	 * The frequency after the VCO stage (parent * m / n) must be in the
+	 * range defined in the programming manual as FVCO:
+	 *
+	 * JZ4740: 100 MHz - 500 MHz
+	 * JZ4780: 300 MHz - 1.5 GHz
+	 * X1000:  300 MHz - 600 MHz (low-band), 500 MHz - 1 GHz (high-band)
+	 * X1600:  600 MHz - 2.4 GHz
+	 * X2000:  2.4 GHz - 4.8 GHz
+	 */
+	m = (rate / MHZ) * od * od1 * n / (parent_rate / MHZ);
 	m = min_t(unsigned int, m, 1 << pll_info->m_bits);
 	m = max_t(unsigned int, m, pll_info->m_offset);
 
 	*pm = m;
 	*pn = n;
 	*pod = od;
+	*pod1 = od1;
 }
 
 static unsigned long
 ingenic_pll_calc(const struct ingenic_cgu_clk_info *clk_info,
 		 unsigned long rate, unsigned long parent_rate,
-		 unsigned int *pm, unsigned int *pn, unsigned int *pod)
+		 unsigned int *pm, unsigned int *pn, unsigned int *pod,
+		 unsigned int *pod1)
 {
 	const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
-	unsigned int m, n, od;
+	unsigned int m, n, od, od1 = 1;
 
 	if (pll_info->calc_m_n_od)
-		(*pll_info->calc_m_n_od)(pll_info, rate, parent_rate, &m, &n, &od);
+		(*pll_info->calc_m_n_od)(pll_info, rate, parent_rate, &m, &n, &od, &od1);
 	else
-		ingenic_pll_calc_m_n_od(pll_info, rate, parent_rate, &m, &n, &od);
+		ingenic_pll_calc_m_n_od(pll_info, rate, parent_rate, &m, &n, &od, &od1);
 
 	if (pm)
 		*pm = m;
@@ -169,9 +215,11 @@ ingenic_pll_calc(const struct ingenic_cgu_clk_info *clk_info,
 		*pn = n;
 	if (pod)
 		*pod = od;
+	if (pod1)
+		*pod1 = od1;
 
 	return div_u64((u64)parent_rate * m * pll_info->rate_multiplier,
-		n * od);
+		n * od * od1);
 }
 
 static int ingenic_pll_determine_rate(struct clk_hw *hw,
@@ -181,7 +229,7 @@ static int ingenic_pll_determine_rate(struct clk_hw *hw,
 	const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
 
 	req->rate = ingenic_pll_calc(clk_info, req->rate, req->best_parent_rate,
-				     NULL, NULL, NULL);
+				     NULL, NULL, NULL, NULL);
 
 	return 0;
 }
@@ -208,12 +256,12 @@ ingenic_pll_set_rate(struct clk_hw *hw, unsigned long req_rate,
 	const struct ingenic_cgu_clk_info *clk_info = to_clk_info(ingenic_clk);
 	const struct ingenic_cgu_pll_info *pll_info = &clk_info->pll;
 	unsigned long rate, flags;
-	unsigned int m, n, od;
+	unsigned int m, n, od, od1;
 	int ret = 0;
 	u32 ctl;
 
 	rate = ingenic_pll_calc(clk_info, req_rate, parent_rate,
-			       &m, &n, &od);
+			       &m, &n, &od, &od1);
 	if (rate != req_rate)
 		pr_info("ingenic-cgu: request '%s' rate %luHz, actual %luHz\n",
 			clk_info->name, req_rate, rate);
@@ -229,7 +277,18 @@ ingenic_pll_set_rate(struct clk_hw *hw, unsigned long req_rate,
 
 	if (pll_info->od_bits > 0) {
 		ctl &= ~(GENMASK(pll_info->od_bits - 1, 0) << pll_info->od_shift);
-		ctl |= pll_info->od_encoding[od - 1] << pll_info->od_shift;
+		if (pll_info->od_encoding)
+			ctl |= pll_info->od_encoding[od - 1] << pll_info->od_shift;
+		else
+			ctl |= (od ? od : 1) << pll_info->od_shift;
+	}
+
+	if (pll_info->od1_bits > 0) {
+		ctl &= ~(GENMASK(pll_info->od1_bits - 1, 0) << pll_info->od1_shift);
+		if (pll_info->od_encoding)
+			ctl |= pll_info->od_encoding[od1 - 1] << pll_info->od1_shift;
+		else
+			ctl |= (od1 ? od1 : 1) << pll_info->od1_shift;
 	}
 
 	writel(ctl, cgu->base + pll_info->reg);
@@ -531,7 +590,7 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
 	struct ingenic_cgu *cgu = ingenic_clk->cgu;
 	unsigned long rate, flags;
 	unsigned int hw_div, div;
-	u32 reg, mask;
+	u32 reg, mask, d_reg;
 	int ret = 0;
 
 	if (clk_info->type & CGU_CLK_DIV) {
@@ -554,6 +613,30 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
 		reg &= ~(mask << clk_info->div.shift);
 		reg |= hw_div << clk_info->div.shift;
 
+		/*
+		 * NOTE: Special treatment for the I2S dividers in the X1600.
+		 * Set the multiplier to 1, imposing an N >= M*2 constraint,
+		 * also setting D to "automatic" calculation. This permits the
+		 * treatment of these dividers as normal dividers, although
+		 * they are configured more similarly to PLLs.
+		 */
+		if (clk_info->mdiv.reg) {
+			if (div < 2) {
+				spin_unlock_irqrestore(&cgu->lock, flags);
+				return -EINVAL;
+			}
+
+			mask = GENMASK(clk_info->mdiv.bits - 1, 0);
+			reg &= ~(mask << clk_info->mdiv.shift);
+			reg |= 1 << clk_info->mdiv.shift;
+
+			if (clk_info->nddiv.reg) {
+				d_reg = readl(cgu->base + clk_info->nddiv.reg);
+				d_reg &= ~(1 << clk_info->nddiv.explicit_d_bit);
+				d_reg |= 1 << clk_info->nddiv.explicit_n_bit;
+			}
+		}
+
 		/* clear the stop bit */
 		if (clk_info->div.stop_bit != -1)
 			reg &= ~BIT(clk_info->div.stop_bit);
@@ -565,6 +648,9 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
 		/* update the hardware */
 		writel(reg, cgu->base + clk_info->div.reg);
 
+		if (clk_info->nddiv.reg)
+			writel(d_reg, cgu->base + clk_info->nddiv.reg);
+
 		/* wait for the change to take effect */
 		if (clk_info->div.busy_bit != -1)
 			ret = ingenic_clk_check_stable(cgu, clk_info);
diff --git a/drivers/clk/ingenic/cgu.h b/drivers/clk/ingenic/cgu.h
index 0d417d69dab71..d5bdd7084b201 100644
--- a/drivers/clk/ingenic/cgu.h
+++ b/drivers/clk/ingenic/cgu.h
@@ -36,6 +36,12 @@
  * @od_bits: the size of the post-VCO divider field in bits, or 0 if no
  *	     OD field exists (then the OD is fixed to 1)
  * @od_max: the maximum post-VCO divider value
+ * @od1_shift: the number of bits to shift the secondary post-VCO divider
+ *             value by (ie. the index of the lowest bit of the post-VCO
+ *             divider value in the PLL's control register)
+ * @od1_bits: the size of the secondary post-VCO divider field in bits, or 0
+ *            if no OD1 field exists (then the OD1 is fixed to 1)
+ * @od1_max: the maximum secondary post-VCO divider value
  * @od_encoding: a pointer to an array mapping post-VCO divider values to
  *               their encoded values in the PLL control register, or -1 for
  *               unsupported values
@@ -56,13 +62,15 @@ struct ingenic_cgu_pll_info {
 	u8 m_shift, m_bits, m_offset;
 	u8 n_shift, n_bits, n_offset;
 	u8 od_shift, od_bits, od_max;
+	u8 od1_shift, od1_bits, od1_max;
 	unsigned bypass_reg;
 	s8 bypass_bit;
 	s8 enable_bit;
 	s8 stable_bit;
 	void (*calc_m_n_od)(const struct ingenic_cgu_pll_info *pll_info,
 			    unsigned long rate, unsigned long parent_rate,
-			    unsigned int *m, unsigned int *n, unsigned int *od);
+			    unsigned int *m, unsigned int *n, unsigned int *od,
+			    unsigned int *od1);
 	void (*set_rate_hook)(const struct ingenic_cgu_pll_info *pll_info,
 			      unsigned long rate, unsigned long parent_rate);
 };
@@ -109,6 +117,36 @@ struct ingenic_cgu_div_info {
 	const u8 *div_table;
 };
 
+/**
+ * struct ingenic_cgu_mdiv_info - information about a divider M parameter
+ * @reg: offset of the divider control register within the CGU
+ * @shift: number of bits to left shift the M divide value by (ie. the index
+ *         of the lowest bit of the divide value within its control
+ *         register)
+ * @div: number to divide the M divider value by (i.e. if the
+ *       effective divider value is the value written to the register
+ *       multiplied by some constant)
+ * @bits: the size of the M divide value in bits
+ */
+struct ingenic_cgu_mdiv_info {
+	unsigned reg;
+	u8 shift;
+	u8 div;
+	u8 bits;
+};
+
+/**
+ * struct ingenic_cgu_nddiv_info - information about divider N and D parameters
+ * @reg: offset of the D divider control register within the CGU
+ * @explicit_n_bit: the bit providing the explicit N divider flag
+ * @explicit_d_bit: the bit providing the explicit D divider flag
+ */
+struct ingenic_cgu_nddiv_info {
+	unsigned reg;
+	u8 explicit_n_bit;
+	u8 explicit_d_bit;
+};
+
 /**
  * struct ingenic_cgu_fixdiv_info - information about a fixed divider
  * @div: the divider applied to the parent clock
@@ -180,6 +218,8 @@ struct ingenic_cgu_clk_info {
 			struct ingenic_cgu_gate_info gate;
 			struct ingenic_cgu_mux_info mux;
 			struct ingenic_cgu_div_info div;
+			struct ingenic_cgu_mdiv_info mdiv;
+			struct ingenic_cgu_nddiv_info nddiv;
 			struct ingenic_cgu_fixdiv_info fixdiv;
 		};
 
diff --git a/drivers/clk/ingenic/x1600-cgu.c b/drivers/clk/ingenic/x1600-cgu.c
new file mode 100644
index 0000000000000..627203f5cabe8
--- /dev/null
+++ b/drivers/clk/ingenic/x1600-cgu.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Ingenic X1600 SoC CGU driver
+ *
+ * Copyright (c) 2013-2015 Imagination Technologies
+ * Author: Paul Burton <paul.burton@mips.com>
+ * Copyright (c) 2020 周琰杰 (Zhou Yanjie) <zhouyanjie@wanyeetech.com>
+ * Copyright (c) 2023, 2024 Paul Boddie <paul@boddie.org.uk>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/rational.h>
+#include <linux/of.h>
+
+#include <dt-bindings/clock/ingenic,x1600-cgu.h>
+
+#include "cgu.h"
+#include "pm.h"
+
+/* CGU register offsets */
+#define CGU_REG_CLOCKCONTROL	0x00
+#define CGU_REG_LCR		0x04
+#define CGU_REG_APLL		0x10
+#define CGU_REG_MPLL		0x14
+#define CGU_REG_EPLL		0x18
+#define CGU_REG_CLKGR0		0x20
+#define CGU_REG_OPCR		0x24
+#define CGU_REG_CLKGR1		0x28
+#define CGU_REG_DDRCDR		0x2c
+#define CGU_REG_CPSPR		0x34
+#define CGU_REG_CPSPPR		0x38
+#define CGU_REG_USBPCR		0x3c
+#define CGU_REG_USBRDT		0x40
+#define CGU_REG_USBVBFIL	0x44
+#define CGU_REG_USBPCR1		0x48
+#define CGU_REG_MACCDR		0x54
+#define CGU_REG_SSICDR		0x5c
+#define CGU_REG_I2S0CDR		0x60
+#define CGU_REG_LPCDR		0x64
+#define CGU_REG_MSC0CDR		0x68
+#define CGU_REG_PWMCDR		0x6c
+#define CGU_REG_I2S0CDR1	0x70
+#define CGU_REG_SFCCDR		0x74
+#define CGU_REG_CIMCDR		0x78
+#define CGU_REG_I2S1CDR		0x7c
+#define CGU_REG_I2S1CDR1	0x80
+#define CGU_REG_APLLFRAC	0x84
+#define CGU_REG_MPLLFRAC	0x88
+#define CGU_REG_EPLLFRAC	0x8c
+#define CGU_REG_CAN0CDR		0xa0
+#define CGU_REG_MSC1CDR		0xa4
+#define CGU_REG_CAN1CDR		0xa8
+#define CGU_REG_CDBUSCDR	0xac
+#define CGU_REG_CMP_INTR	0xb0
+#define CGU_REG_CMP_INTRE	0xb4
+#define CGU_REG_CMP_SFTINT	0xbc
+#define CGU_REG_DRCG		0xd0
+#define CGU_REG_CLOCKSTATUS	0xd4
+#define CGU_REG_MPHY0C		0xe4
+
+/* bits within the OPCR register */
+#define OPCR_SPENDN0		BIT(7)
+#define OPCR_GATEUSBPHYCLK	BIT(23)
+
+static struct ingenic_cgu *cgu;
+
+static int x1600_otg_phy_enable(struct clk_hw *hw)
+{
+	void __iomem *reg_opcr		= cgu->base + CGU_REG_OPCR;
+
+	writel((readl(reg_opcr) & ~OPCR_GATEUSBPHYCLK) | OPCR_SPENDN0, reg_opcr);
+
+	return 0;
+}
+
+static void x1600_otg_phy_disable(struct clk_hw *hw)
+{
+	void __iomem *reg_opcr		= cgu->base + CGU_REG_OPCR;
+
+	writel((readl(reg_opcr) & ~OPCR_SPENDN0) | OPCR_GATEUSBPHYCLK, reg_opcr);
+}
+
+static int x1600_otg_phy_is_enabled(struct clk_hw *hw)
+{
+	void __iomem *reg_opcr		= cgu->base + CGU_REG_OPCR;
+
+	return (readl(reg_opcr) & (OPCR_SPENDN0 | OPCR_GATEUSBPHYCLK)) == OPCR_SPENDN0;
+}
+
+static u8 x1600_otg_phy_get_parent(struct clk_hw * __maybe_unused hw)
+{
+	return 0;
+}
+
+static const struct clk_ops x1600_otg_phy_ops = {
+	.enable		= x1600_otg_phy_enable,
+	.disable	= x1600_otg_phy_disable,
+	.is_enabled	= x1600_otg_phy_is_enabled,
+	.get_parent	= x1600_otg_phy_get_parent,
+};
+
+static void
+x1600_pll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
+		      unsigned long rate, unsigned long parent_rate,
+		      unsigned int *pm, unsigned int *pn, unsigned int *pod,
+		      unsigned int *pod1)
+{
+	const unsigned int od_max = pll_info->od_max - 1;
+	const unsigned int od1_max = pll_info->od1_max - 1;
+	const unsigned int m_max = GENMASK(pll_info->m_bits - 1, 0);
+	const unsigned int n_max = GENMASK(pll_info->n_bits - 1, 0);
+	unsigned long m, n;
+	unsigned int od = 1, od1 = 1;
+	const unsigned long fvco_min = 600000000, fvco_max = 2400000000;
+	unsigned long fvco;
+
+	/*
+	 * Combined output divider range:
+	 *
+	 * ceil(fvco_min / rate) - fvco_max / rate
+	 */
+	unsigned int od_combined_min = (fvco_min + rate) / rate;
+	unsigned int od_combined_max = fvco_max / rate;
+	unsigned int od_combined;
+
+	for (od_combined = od_combined_min; od_combined <= od_combined_max; od_combined++) {
+		od = int_sqrt(od_combined);
+		od1 = od_combined / od;
+
+		/* od == floor(sqrt(od_combined)); od1 >= od */
+		if ((od * od1 == od_combined) && od <= od_max && od1 <= od1_max) {
+			fvco = rate * od_combined;
+			rational_best_approximation(fvco, parent_rate, m_max, n_max, &m, &n);
+			break;
+		}
+	}
+
+	*pm = m;
+	*pn = n;
+	*pod = od;
+	*pod1 = od1;
+}
+
+static const struct ingenic_cgu_clk_info x1600_cgu_clocks[] = {
+
+	/* External clocks */
+
+	[X1600_CLK_EXCLK] = { "ext", CGU_CLK_EXT },
+	[X1600_CLK_RTCLK] = { "rtc", CGU_CLK_EXT },
+	[X1600_CLK_12M] = { "clk12m", CGU_CLK_EXT },
+
+	/* PLLs */
+
+#define DEF_PLL(name) { \
+	.reg = CGU_REG_ ## name, \
+	.rate_multiplier = 1, \
+	.m_shift = 20, \
+	.m_bits = 12, \
+	.m_offset = 0, \
+	.n_shift = 14, \
+	.n_bits = 6, \
+	.n_offset = 0, \
+	.od_shift = 8, \
+	.od_bits = 3, \
+	.od_max = 8, \
+	.od1_shift = 11, \
+	.od1_bits = 3, \
+	.od1_max = 8, \
+	.od_encoding = 0, \
+	.stable_bit = 2, \
+	.enable_bit = 0, \
+	.bypass_bit = -1, \
+	.calc_m_n_od = x1600_pll_calc_m_n_od, \
+}
+
+	[X1600_CLK_APLL] = {
+		"apll", CGU_CLK_PLL,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.pll = DEF_PLL(APLL),
+	},
+
+	[X1600_CLK_MPLL] = {
+		"mpll", CGU_CLK_PLL,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.pll = DEF_PLL(MPLL),
+	},
+
+	[X1600_CLK_EPLL] = {
+		"epll", CGU_CLK_PLL,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.pll = DEF_PLL(EPLL),
+	},
+
+#undef DEF_PLL
+
+	/* Muxes & dividers */
+
+	[X1600_CLK_SCLKA] = {
+		"sclk_a", CGU_CLK_MUX,
+		.parents = { -1, X1600_CLK_EXCLK, X1600_CLK_APLL, -1 },
+		.mux = { CGU_REG_CLOCKCONTROL, 30, 2 },
+	},
+
+	[X1600_CLK_CPUMUX] = {
+		"cpumux", CGU_CLK_MUX,
+		.parents = { -1, X1600_CLK_SCLKA, X1600_CLK_MPLL, -1 },
+		.mux = { CGU_REG_CLOCKCONTROL, 28, 2 },
+	},
+
+	[X1600_CLK_CPU] = {
+		"cpu", CGU_CLK_DIV,
+		/*
+		 * Disabling the CPU clock or any parent clocks will hang the
+		 * system; mark it critical.
+		 */
+		.flags = CLK_IS_CRITICAL,
+		.parents = { X1600_CLK_CPUMUX, -1, -1, -1 },
+		.div = { CGU_REG_CLOCKCONTROL, 0, 1, 4, 22, -1, -1 },
+	},
+
+	[X1600_CLK_L2CACHE] = {
+		"l2cache", CGU_CLK_DIV,
+		/*
+		 * The L2 cache clock is critical if caches are enabled and
+		 * disabling it or any parent clocks will hang the system.
+		 */
+		.flags = CLK_IS_CRITICAL,
+		.parents = { X1600_CLK_CPUMUX, -1, -1, -1 },
+		.div = { CGU_REG_CLOCKCONTROL, 4, 1, 4, -1, -1, -1 },
+	},
+
+	[X1600_CLK_AHB0] = {
+		"ahb0", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { -1, X1600_CLK_SCLKA, X1600_CLK_MPLL, -1 },
+		.mux = { CGU_REG_CLOCKCONTROL, 26, 2 },
+		.div = { CGU_REG_CLOCKCONTROL, 8, 1, 4, 21, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 29 },
+	},
+
+	[X1600_CLK_AHB2PMUX] = {
+		"ahb2_apb_mux", CGU_CLK_MUX,
+		.parents = { -1, X1600_CLK_SCLKA, X1600_CLK_MPLL, -1 },
+		.mux = { CGU_REG_CLOCKCONTROL, 24, 2 },
+	},
+
+	[X1600_CLK_AHB2] = {
+		"ahb2", CGU_CLK_DIV,
+		.parents = { X1600_CLK_AHB2PMUX, -1, -1, -1 },
+		.div = { CGU_REG_CLOCKCONTROL, 12, 1, 4, 20, -1, -1 },
+	},
+
+	[X1600_CLK_PCLK] = {
+		"pclk", CGU_CLK_DIV,
+		.parents = { X1600_CLK_AHB2PMUX, -1, -1, -1 },
+		.div = { CGU_REG_CLOCKCONTROL, 16, 1, 4, 20, -1, -1 },
+	},
+
+	[X1600_CLK_DDR] = {
+		"ddr", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		/*
+		 * Disabling DDR clock or its parents will render DRAM
+		 * inaccessible; mark it critical.
+		 */
+		.flags = CLK_IS_CRITICAL,
+		.parents = { -1, X1600_CLK_SCLKA, X1600_CLK_MPLL, -1 },
+		.mux = { CGU_REG_DDRCDR, 30, 2 },
+		.div = { CGU_REG_DDRCDR, 0, 1, 4, 29, 28, 27 },
+		.gate = { CGU_REG_CLKGR0, 31 },
+	},
+
+	[X1600_CLK_I2S0] = {
+		"i2s0", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_EPLL, -1, -1 },
+		.mux = { CGU_REG_I2S0CDR, 30, 1 },
+		.div = { CGU_REG_I2S0CDR, 20, 1, 9, -1, -1, -1 },
+		.mdiv = { CGU_REG_I2S0CDR, 0, 1, 20 },
+		.nddiv = { CGU_REG_I2S0CDR1, 31, 30 },
+		.gate = { CGU_REG_I2S0CDR, 29, true },
+	},
+
+	[X1600_CLK_I2S1] = {
+		"i2s1", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_EPLL, -1, -1 },
+		.mux = { CGU_REG_I2S1CDR, 30, 1 },
+		.div = { CGU_REG_I2S1CDR, 20, 1, 9, -1, -1, -1 },
+		.mdiv = { CGU_REG_I2S1CDR, 0, 1, 20 },
+		.nddiv = { CGU_REG_I2S1CDR1, 31, 30 },
+		.gate = { CGU_REG_I2S1CDR, 29, true },
+	},
+
+	[X1600_CLK_LCDPIXCLK] = {
+		"lcd0pixclk", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_MPLL,
+			     X1600_CLK_EPLL, -1 },
+		.mux = { CGU_REG_LPCDR, 30, 2 },
+		.div = { CGU_REG_LPCDR, 0, 1, 8, 28, 27, 26 },
+		.gate = { CGU_REG_CLKGR0, 23 },
+	},
+
+	[X1600_CLK_MAC] = {
+		"mac", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_MPLL,
+			     X1600_CLK_EPLL, -1 },
+		.mux = { CGU_REG_MACCDR, 30, 2 },
+		.div = { CGU_REG_MACCDR, 0, 1, 8, 28, 27, 26 },
+		.gate = { CGU_REG_CLKGR1, 23 },
+	},
+
+	[X1600_CLK_MSC0] = {
+		"msc0", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_MPLL,
+			     X1600_CLK_EPLL, -1 },
+		.mux = { CGU_REG_MSC0CDR, 30, 2 },
+		.div = { CGU_REG_MSC0CDR, 0, 2, 8, 29, 28, 27 },
+		.gate = { CGU_REG_CLKGR0, 4 },
+	},
+
+	[X1600_CLK_MSC1] = {
+		"msc1", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_MPLL,
+			     X1600_CLK_EPLL, -1 },
+		.mux = { CGU_REG_MSC1CDR, 30, 2 },
+		.div = { CGU_REG_MSC1CDR, 0, 2, 8, 29, 28, 27 },
+		.gate = { CGU_REG_CLKGR0, 5 },
+	},
+
+	[X1600_CLK_SSI0] = {
+		"ssi", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_MPLL,
+			     X1600_CLK_EPLL, -1 },
+		.mux = { CGU_REG_SSICDR, 30, 2 },
+		.div = { CGU_REG_SSICDR, 0, 1, 8, 29, 28, 27 },
+		.gate = { CGU_REG_CLKGR0, 19 },
+	},
+
+	[X1600_CLK_CIMMCLK] = {
+		"cim_mclk", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE,
+		.parents = { X1600_CLK_SCLKA, X1600_CLK_MPLL,
+			     X1600_CLK_EPLL, -1 },
+		.mux = { CGU_REG_CIMCDR, 30, 2 },
+		.div = { CGU_REG_CIMCDR, 0, 1, 8, 30, 29, 28 },
+		.gate = { CGU_REG_CLKGR0, 22 },
+	},
+
+	[X1600_CLK_EXCLK_DIV512] = {
+		"exclk_div512", CGU_CLK_FIXDIV,
+		.parents = { X1600_CLK_EXCLK },
+		.fixdiv = { 512 },
+	},
+
+	[X1600_CLK_RTC] = {
+		"rtc_ercs", CGU_CLK_MUX | CGU_CLK_GATE,
+		.parents = { X1600_CLK_EXCLK_DIV512, X1600_CLK_RTCLK },
+		.mux = { CGU_REG_OPCR, 2, 1},
+	},
+
+	/* Custom (SoC-specific) OTG PHY */
+
+	[X1600_CLK_OTGPHY] = {
+		"otg_phy", CGU_CLK_CUSTOM,
+		.parents = { X1600_CLK_12M },
+		.custom = { &x1600_otg_phy_ops },
+	},
+
+	/* Gate-only clocks */
+
+	[X1600_CLK_NEMC] = {
+		"nemc", CGU_CLK_GATE,
+		.parents = { X1600_CLK_AHB2, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 0 },
+	},
+
+	[X1600_CLK_OTG0] = {
+		"otg0", CGU_CLK_GATE,
+		.parents = { X1600_CLK_AHB2, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 3 },
+	},
+
+	[X1600_CLK_SMB0] = {
+		"smb0", CGU_CLK_GATE,
+		.parents = { X1600_CLK_PCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 7 },
+	},
+
+	[X1600_CLK_SMB1] = {
+		"smb1", CGU_CLK_GATE,
+		.parents = { X1600_CLK_PCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 8 },
+	},
+
+	[X1600_CLK_AIC] = {
+		"aic", CGU_CLK_GATE,
+		.parents = { X1600_CLK_AHB2, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 11 },
+	},
+
+	[X1600_CLK_I2S0_RX] = {
+		"i2s0_rx", CGU_CLK_GATE,
+		.parents = { X1600_CLK_I2S0, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR1, 8 },
+	},
+
+	[X1600_CLK_I2S0_TX] = {
+		"i2s0_tx", CGU_CLK_GATE,
+		.parents = { X1600_CLK_I2S1, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR1, 9 },
+	},
+
+	[X1600_CLK_SADC] = {
+		"sadc", CGU_CLK_GATE,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 14 },
+	},
+
+	[X1600_CLK_UART0] = {
+		"uart0", CGU_CLK_GATE,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 14 },
+	},
+
+	[X1600_CLK_UART1] = {
+		"uart1", CGU_CLK_GATE,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 15 },
+	},
+
+	[X1600_CLK_UART2] = {
+		"uart2", CGU_CLK_GATE,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 16 },
+	},
+
+	[X1600_CLK_UART3] = {
+		"uart3", CGU_CLK_GATE,
+		.parents = { X1600_CLK_EXCLK, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR1, 16 },
+	},
+
+	[X1600_CLK_PDMA] = {
+		"pdma", CGU_CLK_GATE,
+		.parents = { X1600_CLK_AHB2, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR0, 21 },
+	},
+
+	[X1600_CLK_AES] = {
+		"aes", CGU_CLK_GATE,
+		.parents = { X1600_CLK_AHB2, -1, -1, -1 },
+		.gate = { CGU_REG_CLKGR1, 24 },
+	},
+
+};
+
+static void __init x1600_cgu_init(struct device_node *np)
+{
+	int retval;
+
+	cgu = ingenic_cgu_new(x1600_cgu_clocks,
+			      ARRAY_SIZE(x1600_cgu_clocks), np);
+	if (!cgu) {
+		pr_err("%s: failed to initialise CGU\n", __func__);
+		return;
+	}
+
+	retval = ingenic_cgu_register_clocks(cgu);
+	if (retval) {
+		pr_err("%s: failed to register CGU Clocks\n", __func__);
+		return;
+	}
+
+	ingenic_cgu_register_syscore(cgu);
+}
+
+CLK_OF_DECLARE_DRIVER(x1600_cgu, "ingenic,x1600-cgu", x1600_cgu_init);
-- 
2.56.0


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

* [PATCH 3/4] clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework changes
  2026-10-25 19:18 [PATCH 0/4] clk / clocksource: Add Ingenic X1600 SoC support H. Nikolaus Schaller
  2026-10-25 19:18 ` [PATCH 1/4] dt-bindings: clock: Add Ingenic X1600 CGU bindings H. Nikolaus Schaller
  2026-10-25 19:18 ` [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support H. Nikolaus Schaller
@ 2026-10-25 19:18 ` H. Nikolaus Schaller
  2026-10-25 22:54   ` Paul Cercueil
  2026-10-25 19:18 ` [PATCH 4/4] clk: ingenic: x1000-cgu: " H. Nikolaus Schaller
  3 siblings, 1 reply; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-25 19:18 UTC (permalink / raw)
  To: Stephen Boyd, Brian Masney, Jerome Brunet, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Paul Cercueil
  Cc: linux-clk, devicetree, linux-kernel, linux-mips, letux-kernel,
	kernel, H. Nikolaus Schaller

The common Ingenic CGU core framework was updated to support a
secondary post-VCO divider (OD1), which extended the signature of the
calc_m_n_od() callback.

Update the JZ4760 CGU driver implementation to accept the new pod1
argument, preventing compilation errors. The parameter remains unused
on this specific SoC architecture.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 drivers/clk/ingenic/jz4760-cgu.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/clk/ingenic/jz4760-cgu.c b/drivers/clk/ingenic/jz4760-cgu.c
index 372fe4b079927..52cc5ce150048 100644
--- a/drivers/clk/ingenic/jz4760-cgu.c
+++ b/drivers/clk/ingenic/jz4760-cgu.c
@@ -56,7 +56,8 @@ static const u8 jz4760_cgu_pll_half_div_table[] = {
 static void
 jz4760_cgu_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
 		       unsigned long rate, unsigned long parent_rate,
-		       unsigned int *pm, unsigned int *pn, unsigned int *pod)
+		       unsigned int *pm, unsigned int *pn, unsigned int *pod,
+		       unsigned int *pod1)
 {
 	unsigned int m, n, od, m_max = (1 << pll_info->m_bits) - 1;
 
-- 
2.56.0


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

* [PATCH 4/4] clk: ingenic: x1000-cgu: Adapt calc_m_n_od() to core framework changes
  2026-10-25 19:18 [PATCH 0/4] clk / clocksource: Add Ingenic X1600 SoC support H. Nikolaus Schaller
                   ` (2 preceding siblings ...)
  2026-10-25 19:18 ` [PATCH 3/4] clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework changes H. Nikolaus Schaller
@ 2026-10-25 19:18 ` H. Nikolaus Schaller
  3 siblings, 0 replies; 7+ messages in thread
From: H. Nikolaus Schaller @ 2026-10-25 19:18 UTC (permalink / raw)
  To: Stephen Boyd, Brian Masney, Jerome Brunet, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Paul Cercueil
  Cc: linux-clk, devicetree, linux-kernel, linux-mips, letux-kernel,
	kernel, H. Nikolaus Schaller

The common Ingenic CGU core framework was updated to support a
secondary post-VCO divider (OD1), which extended the signature of the
calc_m_n_od() callback.

Update the X1000 CGU driver implementation to accept the new pod1
argument, preventing compilation errors. The parameter remains unused
on this specific SoC architecture.

Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
---
 drivers/clk/ingenic/x1000-cgu.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/clk/ingenic/x1000-cgu.c b/drivers/clk/ingenic/x1000-cgu.c
index d89bdfb7c219a..cb82d8e6470b6 100644
--- a/drivers/clk/ingenic/x1000-cgu.c
+++ b/drivers/clk/ingenic/x1000-cgu.c
@@ -173,7 +173,8 @@ static const struct clk_ops x1000_otg_phy_ops = {
 static void
 x1000_i2spll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
 			 unsigned long rate, unsigned long parent_rate,
-			 unsigned int *pm, unsigned int *pn, unsigned int *pod)
+			 unsigned int *pm, unsigned int *pn, unsigned int *pod,
+			 unsigned int *pod1)
 {
 	const unsigned long m_max = GENMASK(pll_info->m_bits - 1, 0);
 	const unsigned long n_max = GENMASK(pll_info->n_bits - 1, 0);
-- 
2.56.0


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

* Re: [PATCH 3/4] clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework changes
  2026-10-25 19:18 ` [PATCH 3/4] clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework changes H. Nikolaus Schaller
@ 2026-10-25 22:54   ` Paul Cercueil
  0 siblings, 0 replies; 7+ messages in thread
From: Paul Cercueil @ 2026-10-25 22:54 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Stephen Boyd, Brian Masney, Jerome Brunet,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley
  Cc: linux-clk, devicetree, linux-kernel, linux-mips, letux-kernel, kernel

Hi Nikolaus,

Le dimanche 25 octobre 2026 à 20:18 +0100, H. Nikolaus Schaller a
écrit :
> The common Ingenic CGU core framework was updated to support a
> secondary post-VCO divider (OD1), which extended the signature of the
> calc_m_n_od() callback.
> 
> Update the JZ4760 CGU driver implementation to accept the new pod1
> argument, preventing compilation errors. The parameter remains unused
> on this specific SoC architecture.
> 
> Signed-off-by: H. Nikolaus Schaller <hns@goldelico.com>
> ---
>  drivers/clk/ingenic/jz4760-cgu.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/clk/ingenic/jz4760-cgu.c
> b/drivers/clk/ingenic/jz4760-cgu.c
> index 372fe4b079927..52cc5ce150048 100644
> --- a/drivers/clk/ingenic/jz4760-cgu.c
> +++ b/drivers/clk/ingenic/jz4760-cgu.c
> @@ -56,7 +56,8 @@ static const u8 jz4760_cgu_pll_half_div_table[] = {
>  static void
>  jz4760_cgu_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
>  		       unsigned long rate, unsigned long
> parent_rate,
> -		       unsigned int *pm, unsigned int *pn, unsigned
> int *pod)
> +		       unsigned int *pm, unsigned int *pn, unsigned
> int *pod,
> +		       unsigned int *pod1)
>  {
>  	unsigned int m, n, od, m_max = (1 << pll_info->m_bits) - 1;
>  

Well you can't update a callback prototype in patch 2/4 then update the
implementations in 3/4, the kernel has to be buildable after each
patch.

Cheers,
-Paul

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

* Re: [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support
  2026-10-25 19:18 ` [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support H. Nikolaus Schaller
@ 2026-10-26 10:16   ` kernel test robot
  0 siblings, 0 replies; 7+ messages in thread
From: kernel test robot @ 2026-10-26 10:16 UTC (permalink / raw)
  To: H. Nikolaus Schaller, Stephen Boyd, Brian Masney, Jerome Brunet,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Paul Cercueil
  Cc: llvm, oe-kbuild-all, linux-clk, devicetree, linux-kernel,
	linux-mips, letux-kernel, kernel, Paul Boddie,
	H. Nikolaus Schaller

Hi Nikolaus,

kernel test robot noticed the following build warnings:

[auto build test WARNING on clk/clk-next]
[also build test WARNING on robh/for-next linus/master v7.3 next-20261023]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/H-Nikolaus-Schaller/dt-bindings-clock-Add-Ingenic-X1600-CGU-bindings/20261025-201824
base:   https://git.kernel.org/pub/scm/linux/kernel/git/clk/linux.git clk-next
patch link:    https://lore.kernel.org/r/50983be55e467076e83e3ffc37b30fc4e5648e15.1792955907.git.hns%40goldelico.com
patch subject: [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support
config: hexagon-allmodconfig (https://download.01.org/0day-ci/archive/20261026/202610261821.QhaEUvEU-lkp@intel.com/config)
compiler: clang version 24.0.0git (https://github.com/llvm/llvm-project 186e70f4e6a1521991d2340d3d49b23b1beff11f)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261026/202610261821.QhaEUvEU-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202610261821.QhaEUvEU-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/clk/ingenic/x1600-cgu.c:129:38: warning: variable 'm' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
     129 |         for (od_combined = od_combined_min; od_combined <= od_combined_max; od_combined++) {
         |                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/clk/ingenic/x1600-cgu.c:141:8: note: uninitialized use occurs here
     141 |         *pm = m;
         |               ^
   drivers/clk/ingenic/x1600-cgu.c:129:38: note: remove the condition if it is always true
     129 |         for (od_combined = od_combined_min; od_combined <= od_combined_max; od_combined++) {
         |                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/clk/ingenic/x1600-cgu.c:115:17: note: initialize the variable 'm' to silence this warning
     115 |         unsigned long m, n;
         |                        ^
         |                         = 0
>> drivers/clk/ingenic/x1600-cgu.c:129:38: warning: variable 'n' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
     129 |         for (od_combined = od_combined_min; od_combined <= od_combined_max; od_combined++) {
         |                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/clk/ingenic/x1600-cgu.c:142:8: note: uninitialized use occurs here
     142 |         *pn = n;
         |               ^
   drivers/clk/ingenic/x1600-cgu.c:129:38: note: remove the condition if it is always true
     129 |         for (od_combined = od_combined_min; od_combined <= od_combined_max; od_combined++) {
         |                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/clk/ingenic/x1600-cgu.c:115:20: note: initialize the variable 'n' to silence this warning
     115 |         unsigned long m, n;
         |                           ^
         |                            = 0
   2 warnings generated.


vim +129 drivers/clk/ingenic/x1600-cgu.c

   104	
   105	static void
   106	x1600_pll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info,
   107			      unsigned long rate, unsigned long parent_rate,
   108			      unsigned int *pm, unsigned int *pn, unsigned int *pod,
   109			      unsigned int *pod1)
   110	{
   111		const unsigned int od_max = pll_info->od_max - 1;
   112		const unsigned int od1_max = pll_info->od1_max - 1;
   113		const unsigned int m_max = GENMASK(pll_info->m_bits - 1, 0);
   114		const unsigned int n_max = GENMASK(pll_info->n_bits - 1, 0);
   115		unsigned long m, n;
   116		unsigned int od = 1, od1 = 1;
   117		const unsigned long fvco_min = 600000000, fvco_max = 2400000000;
   118		unsigned long fvco;
   119	
   120		/*
   121		 * Combined output divider range:
   122		 *
   123		 * ceil(fvco_min / rate) - fvco_max / rate
   124		 */
   125		unsigned int od_combined_min = (fvco_min + rate) / rate;
   126		unsigned int od_combined_max = fvco_max / rate;
   127		unsigned int od_combined;
   128	
 > 129		for (od_combined = od_combined_min; od_combined <= od_combined_max; od_combined++) {
   130			od = int_sqrt(od_combined);
   131			od1 = od_combined / od;
   132	
   133			/* od == floor(sqrt(od_combined)); od1 >= od */
   134			if ((od * od1 == od_combined) && od <= od_max && od1 <= od1_max) {
   135				fvco = rate * od_combined;
   136				rational_best_approximation(fvco, parent_rate, m_max, n_max, &m, &n);
   137				break;
   138			}
   139		}
   140	
   141		*pm = m;
   142		*pn = n;
   143		*pod = od;
   144		*pod1 = od1;
   145	}
   146	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

end of thread, other threads:[~2026-10-26 10:16 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-25 19:18 [PATCH 0/4] clk / clocksource: Add Ingenic X1600 SoC support H. Nikolaus Schaller
2026-10-25 19:18 ` [PATCH 1/4] dt-bindings: clock: Add Ingenic X1600 CGU bindings H. Nikolaus Schaller
2026-10-25 19:18 ` [PATCH 2/4] clk: ingenic: Add Ingenic X1600 CGU clock driver support H. Nikolaus Schaller
2026-10-26 10:16   ` kernel test robot
2026-10-25 19:18 ` [PATCH 3/4] clk: ingenic: jz4760-cgu: Adapt calc_m_n_od() to core framework changes H. Nikolaus Schaller
2026-10-25 22:54   ` Paul Cercueil
2026-10-25 19:18 ` [PATCH 4/4] clk: ingenic: x1000-cgu: " H. Nikolaus Schaller

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

all inboxes | Powered by JetHome®