linux-riscv.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 0/5] Add clock controller support for SpacemiT K1
@ 2025-03-06 17:57 Haylen Chu
  2025-03-06 17:57 ` [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu
                   ` (4 more replies)
  0 siblings, 5 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-06 17:57 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu

The clock tree of SpacemiT K1 is managed by several independent
multifunction devices, some of them are

- Application Power Manage Unit, APMU
- Main Power Manage Unit, MPMU
- APB Bus Clock Unit, APBC
- APB Spare, APBS

These four devices provide hardware bits for three purposes: power
management, reset signals and clocks. Not every device is capable of all
the three functionalities,

- APMU, MPMU: power, reset, clock
- APBC: clock, reset
- APBS: clock (PLL clocks)

This series adds support for clock hardwares in these four regions,
which covers most peripherals except DDR and the realtime processor.

Tested on BananaPi-F3 board. With some out-of-tree drivers, I've
successfully brought up I2C, RTC, MMC and ethernet controllers. A clock
tree dump could be obtained here[1].

[1]: https://gist.github.com/heylenayy/4c88630454d5ad26c9336592673eb187

Changed from v4
- bindings:
  - Drop CLK_*_NUM macros from binding headers
  - Rename spacemit,k1-ccu.yaml to spacemit,k1-pll.yaml, change to
    describe only the PLL in APBS region
  - k1-syscon.yaml
    - drop spacemit,k1-syscon-apbs, it should be the PLL device
    - drop child nodes
    - describe the syscons as clock, reset and power-domain controllers
    - drop "syscon" from the compatible list, as these syscons aren't
      compatible with the generic one
- driver:
  - misc style fixes and naming improvements
  - drop unused fields from data structures
  - drop unused clock types: CCU_DDN_GATE
  - ddn type:
    - improve the comments
    - dynamically calculate appropriate rates
    - hardcode the x2 factor
  - mix type
    - drop val_{disable,enable} for gate subtype
    - drop unncessary polling when enabling a gate
    - encode subtypes directly in struct ccu_mix
    - generate clock names from identifiers of the data structure
    - rename CCU_DIV2_FC_MUX_GATE_DEFINE to CCU_DIV_SPLIT_FC_MUX_GATE
  - pll type:
    - correctly claim the parent clock
    - make rate tables const
    - drop SWCR2-related fields
    - combine fields of registers as a whole instead of working with
      each field
  - clock tree for k1:
    - removed duplicated offsets
    - drop the placeholder 1:1 factor, pll1_d7_351p8
    - workaround the quirk of TWSI8 clocks
    - fix the definition of ripc_clk, wdt_bus_clk, dpu_bit_clk and
      timers_*_clk
    - drop structure spacemit_ccu_priv and spacemit_ccu_data
    - rework clock registration
    - split the PCIe clocks correctly (there're three distinct clocks
      for each PCIe port)
- devicetree:
  - adapt the new binding
- Link to v4: https://lore.kernel.org/all/20250103215636.19967-2-heylenay@4d2.org/

Changed from v3
- spacemit,k1-ccu binding
  - allow spacemit,mpmu property only for controllers requiring it
    (spacemit,k1-ccu-apbs)
- spacemit,k1-syscon binding
  - drop unnecessary *-cells properties
  - drop unrelated nodes in the example
- driver
  - remove unnecessary divisions during rate calucalation in ccu_ddn.c
  - use independent clk_ops for different ddn/mix variants, drop
    reg_type field in struct ccu_common
  - make the register containing frequency change bit a sperate field in
    ccu_common
  - unify DIV_MFC_MUX_GATE and DIV_FC_MUX_GATE
  - implement a correct determine_rate() for mix type
  - avoid reparenting in set_rate() for mix type
  - fix build failure when SPACEMIT_CCU and SPACEMIT_CCU_K1 are
    configured differently
- use "osc" instead of "osc_32k" in clock input names
- misc style fixes
- Link to v3: https://lore.kernel.org/all/20241126143125.9980-2-heylenay@4d2.org/

Changed from v2
- dt-binding fixes
  - drop clocks marked as deprecated by the vendor (CLK_JPF_4KAFBC and
    CLK_JPF_2KAFBC)
  - add binding of missing bus clocks
  - change input clocks to use frequency-aware and more precise names
  - mark input clocks and their names as required
  - move the example to the (parent) syscon node and complete it
  - misc style fixes
- misc improvements in code
- drop unnecessary spinlock in the driver
- implement missing bus clocks
- Link to v2: https://lore.kernel.org/all/SEYPR01MB4221829A2CD4D4C1704BABD7D7602@SEYPR01MB4221.apcprd01.prod.exchangelabs.com/

Changed from v1
- add SoC prefix (k1)
- relicense dt-binding header
- misc fixes and style improvements for dt-binding
- document spacemit,k1-syscon
- implement all APBS, MPMU, APBC and APMU clocks
- code cleanup
- Link to v1: https://lore.kernel.org/all/SEYPR01MB4221B3178F5233EAB5149E41D7902@SEYPR01MB4221.apcprd01.prod.exchangelabs.com/

Haylen Chu (5):
  dt-bindings: soc: spacemit: Add spacemit,k1-syscon
  dt-bindings: clock: spacemit: Add spacemit,k1-pll
  clk: spacemit: Add clock support for Spacemit K1 SoC
  clk: spacemit: k1: Add TWSI8 bus and function clocks
  riscv: dts: spacemit: Add clock tree for Spacemit K1

 .../bindings/clock/spacemit,k1-pll.yaml       |   50 +
 .../soc/spacemit/spacemit,k1-syscon.yaml      |   80 +
 arch/riscv/boot/dts/spacemit/k1.dtsi          |   79 +
 drivers/clk/Kconfig                           |    1 +
 drivers/clk/Makefile                          |    1 +
 drivers/clk/spacemit/Kconfig                  |   20 +
 drivers/clk/spacemit/Makefile                 |    5 +
 drivers/clk/spacemit/ccu-k1.c                 | 1720 +++++++++++++++++
 drivers/clk/spacemit/ccu_common.h             |   47 +
 drivers/clk/spacemit/ccu_ddn.c                |   80 +
 drivers/clk/spacemit/ccu_ddn.h                |   48 +
 drivers/clk/spacemit/ccu_mix.c                |  284 +++
 drivers/clk/spacemit/ccu_mix.h                |  246 +++
 drivers/clk/spacemit/ccu_pll.c                |  146 ++
 drivers/clk/spacemit/ccu_pll.h                |   76 +
 include/dt-bindings/clock/spacemit,k1-ccu.h   |  247 +++
 16 files changed, 3130 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
 create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml
 create mode 100644 drivers/clk/spacemit/Kconfig
 create mode 100644 drivers/clk/spacemit/Makefile
 create mode 100644 drivers/clk/spacemit/ccu-k1.c
 create mode 100644 drivers/clk/spacemit/ccu_common.h
 create mode 100644 drivers/clk/spacemit/ccu_ddn.c
 create mode 100644 drivers/clk/spacemit/ccu_ddn.h
 create mode 100644 drivers/clk/spacemit/ccu_mix.c
 create mode 100644 drivers/clk/spacemit/ccu_mix.h
 create mode 100644 drivers/clk/spacemit/ccu_pll.c
 create mode 100644 drivers/clk/spacemit/ccu_pll.h
 create mode 100644 include/dt-bindings/clock/spacemit,k1-ccu.h

-- 
2.48.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon
  2025-03-06 17:57 [PATCH v5 0/5] Add clock controller support for SpacemiT K1 Haylen Chu
@ 2025-03-06 17:57 ` Haylen Chu
  2025-03-07  8:16   ` Krzysztof Kozlowski
  2025-03-06 17:57 ` [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll Haylen Chu
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 26+ messages in thread
From: Haylen Chu @ 2025-03-06 17:57 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu

Document APMU, MPMU and APBC syscons found on Spacemit K1 SoC, which are
capable of generating clock and reset signals. Additionally, APMU and MPMU
manage power domains.

Signed-off-by: Haylen Chu <heylenay@4d2.org>
---
 .../soc/spacemit/spacemit,k1-syscon.yaml      |  80 +++++++
 include/dt-bindings/clock/spacemit,k1-ccu.h   | 210 ++++++++++++++++++
 2 files changed, 290 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml
 create mode 100644 include/dt-bindings/clock/spacemit,k1-ccu.h

diff --git a/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml
new file mode 100644
index 000000000000..07a6728e6f86
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/spacemit/spacemit,k1-syscon.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Spacemit K1 SoC System Controller
+
+maintainers:
+  - Haylen Chu <heylenay@4d2.org>
+
+description:
+  System controllers found on Spacemit K1 SoC, which are capable of
+  clock, reset and power-management functions.
+
+properties:
+  compatible:
+    enum:
+      - spacemit,k1-syscon-apbc
+      - spacemit,k1-syscon-apmu
+      - spacemit,k1-syscon-mpmu
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 4
+
+  clock-names:
+    items:
+      - const: osc
+      - const: vctcxo_1m
+      - const: vctcxo_3m
+      - const: vctcxo_24m
+
+  "#clock-cells":
+    const: 1
+    description:
+      See <dt-bindings/clock/spacemit,k1-ccu.h> for valid indices.
+
+  "#power-domain-cells":
+    const: 1
+
+  "#reset-cells":
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - "#clock-cells"
+  - "#reset-cells"
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: spacemit,k1-syscon-apbc
+    then:
+      properties:
+        "#power-domain-cells": false
+    else:
+      required:
+        - "#power-domain-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    system-controller@d4050000 {
+        compatible = "spacemit,k1-syscon-mpmu";
+        reg = <0xd4050000 0x209c>;
+        clocks = <&osc>, <&vctcxo_1m>, <&vctcxo_3m>, <&vctcxo_24m>;
+        clock-names = "osc", "vctcxo_1m", "vctcxo_3m", "vctcxo_24m";
+        #clock-cells = <1>;
+        #power-domain-cells = <1>;
+        #reset-cells = <1>;
+    };
diff --git a/include/dt-bindings/clock/spacemit,k1-ccu.h b/include/dt-bindings/clock/spacemit,k1-ccu.h
new file mode 100644
index 000000000000..1f8b01db44ca
--- /dev/null
+++ b/include/dt-bindings/clock/spacemit,k1-ccu.h
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (C) 2024 Haylen Chu <heylenay@outlook.com>
+ */
+
+#ifndef _DT_BINDINGS_SPACEMIT_CCU_H_
+#define _DT_BINDINGS_SPACEMIT_CCU_H_
+
+/*	MPMU clocks	*/
+#define CLK_PLL1_307P2		0
+#define CLK_PLL1_76P8		1
+#define CLK_PLL1_61P44		2
+#define CLK_PLL1_153P6		3
+#define CLK_PLL1_102P4		4
+#define CLK_PLL1_51P2		5
+#define CLK_PLL1_51P2_AP	6
+#define CLK_PLL1_57P6		7
+#define CLK_PLL1_25P6		8
+#define CLK_PLL1_12P8		9
+#define CLK_PLL1_12P8_WDT	10
+#define CLK_PLL1_6P4		11
+#define CLK_PLL1_3P2		12
+#define CLK_PLL1_1P6		13
+#define CLK_PLL1_0P8		14
+#define CLK_PLL1_409P6		15
+#define CLK_PLL1_204P8		16
+#define CLK_PLL1_491		17
+#define CLK_PLL1_245P76		18
+#define CLK_PLL1_614		19
+#define CLK_PLL1_47P26		20
+#define CLK_PLL1_31P5		21
+#define CLK_PLL1_819		22
+#define CLK_PLL1_1228		23
+#define CLK_SLOW_UART		24
+#define CLK_SLOW_UART1		25
+#define CLK_SLOW_UART2		26
+#define CLK_WDT			27
+#define CLK_RIPC		28
+#define CLK_I2S_SYSCLK		29
+#define CLK_I2S_BCLK		30
+#define CLK_APB			31
+#define CLK_WDT_BUS		32
+
+/*	APBC clocks	*/
+#define CLK_UART0		0
+#define CLK_UART2		1
+#define CLK_UART3		2
+#define CLK_UART4		3
+#define CLK_UART5		4
+#define CLK_UART6		5
+#define CLK_UART7		6
+#define CLK_UART8		7
+#define CLK_UART9		8
+#define CLK_GPIO		9
+#define CLK_PWM0		10
+#define CLK_PWM1		11
+#define CLK_PWM2		12
+#define CLK_PWM3		13
+#define CLK_PWM4		14
+#define CLK_PWM5		15
+#define CLK_PWM6		16
+#define CLK_PWM7		17
+#define CLK_PWM8		18
+#define CLK_PWM9		19
+#define CLK_PWM10		20
+#define CLK_PWM11		21
+#define CLK_PWM12		22
+#define CLK_PWM13		23
+#define CLK_PWM14		24
+#define CLK_PWM15		25
+#define CLK_PWM16		26
+#define CLK_PWM17		27
+#define CLK_PWM18		28
+#define CLK_PWM19		29
+#define CLK_SSP3		30
+#define CLK_RTC			31
+#define CLK_TWSI0		32
+#define CLK_TWSI1		33
+#define CLK_TWSI2		34
+#define CLK_TWSI4		35
+#define CLK_TWSI5		36
+#define CLK_TWSI6		37
+#define CLK_TWSI7		38
+#define CLK_TWSI8		39
+#define CLK_TIMERS1		40
+#define CLK_TIMERS2		41
+#define CLK_AIB			42
+#define CLK_ONEWIRE		43
+#define CLK_SSPA0		44
+#define CLK_SSPA1		45
+#define CLK_DRO			46
+#define CLK_IR			47
+#define CLK_TSEN		48
+#define CLK_IPC_AP2AUD		49
+#define CLK_CAN0		50
+#define CLK_CAN0_BUS		51
+#define CLK_UART0_BUS		52
+#define CLK_UART2_BUS		53
+#define CLK_UART3_BUS		54
+#define CLK_UART4_BUS		55
+#define CLK_UART5_BUS		56
+#define CLK_UART6_BUS		57
+#define CLK_UART7_BUS		58
+#define CLK_UART8_BUS		59
+#define CLK_UART9_BUS		60
+#define CLK_GPIO_BUS		61
+#define CLK_PWM0_BUS		62
+#define CLK_PWM1_BUS		63
+#define CLK_PWM2_BUS		64
+#define CLK_PWM3_BUS		65
+#define CLK_PWM4_BUS		66
+#define CLK_PWM5_BUS		67
+#define CLK_PWM6_BUS		68
+#define CLK_PWM7_BUS		69
+#define CLK_PWM8_BUS		70
+#define CLK_PWM9_BUS		71
+#define CLK_PWM10_BUS		72
+#define CLK_PWM11_BUS		73
+#define CLK_PWM12_BUS		74
+#define CLK_PWM13_BUS		75
+#define CLK_PWM14_BUS		76
+#define CLK_PWM15_BUS		77
+#define CLK_PWM16_BUS		78
+#define CLK_PWM17_BUS		79
+#define CLK_PWM18_BUS		80
+#define CLK_PWM19_BUS		81
+#define CLK_SSP3_BUS		82
+#define CLK_RTC_BUS		83
+#define CLK_TWSI0_BUS		84
+#define CLK_TWSI1_BUS		85
+#define CLK_TWSI2_BUS		86
+#define CLK_TWSI4_BUS		87
+#define CLK_TWSI5_BUS		88
+#define CLK_TWSI6_BUS		89
+#define CLK_TWSI7_BUS		90
+#define CLK_TWSI8_BUS		91
+#define CLK_TIMERS1_BUS		92
+#define CLK_TIMERS2_BUS		93
+#define CLK_AIB_BUS		94
+#define CLK_ONEWIRE_BUS		95
+#define CLK_SSPA0_BUS		96
+#define CLK_SSPA1_BUS		97
+#define CLK_TSEN_BUS		98
+#define CLK_IPC_AP2AUD_BUS	99
+
+/*	APMU clocks	*/
+#define CLK_CCI550		0
+#define CLK_CPU_C0_HI		1
+#define CLK_CPU_C0_CORE		2
+#define CLK_CPU_C0_ACE		3
+#define CLK_CPU_C0_TCM		4
+#define CLK_CPU_C1_HI		5
+#define CLK_CPU_C1_CORE		6
+#define CLK_CPU_C1_ACE		7
+#define CLK_CCIC_4X		8
+#define CLK_CCIC1PHY		9
+#define CLK_SDH_AXI		10
+#define CLK_SDH0		11
+#define CLK_SDH1		12
+#define CLK_SDH2		13
+#define CLK_USB_P1		14
+#define CLK_USB_AXI		15
+#define CLK_USB30		16
+#define CLK_QSPI		17
+#define CLK_QSPI_BUS		18
+#define CLK_DMA			19
+#define CLK_AES			20
+#define CLK_VPU			21
+#define CLK_GPU			22
+#define CLK_EMMC		23
+#define CLK_EMMC_X		24
+#define CLK_AUDIO		25
+#define CLK_HDMI		26
+#define CLK_PMUA_ACLK		27
+#define CLK_PCIE0_MASTER	28
+#define CLK_PCIE0_SLAVE		29
+#define CLK_PCIE0_DBI		30
+#define CLK_PCIE1_MASTER	31
+#define CLK_PCIE1_SLAVE		32
+#define CLK_PCIE1_DBI		33
+#define CLK_PCIE2_MASTER	34
+#define CLK_PCIE2_SLAVE		35
+#define CLK_PCIE2_DBI		36
+#define CLK_EMAC0_BUS		37
+#define CLK_EMAC0_PTP		38
+#define CLK_EMAC1_BUS		39
+#define CLK_EMAC1_PTP		40
+#define CLK_JPG			41
+#define CLK_CCIC2PHY		42
+#define CLK_CCIC3PHY		43
+#define CLK_CSI			44
+#define CLK_CAMM0		45
+#define CLK_CAMM1		46
+#define CLK_CAMM2		47
+#define CLK_ISP_CPP		48
+#define CLK_ISP_BUS		49
+#define CLK_ISP			50
+#define CLK_DPU_MCLK		51
+#define CLK_DPU_ESC		52
+#define CLK_DPU_BIT		53
+#define CLK_DPU_PXCLK		54
+#define CLK_DPU_HCLK		55
+#define CLK_DPU_SPI		56
+#define CLK_DPU_SPI_HBUS	57
+#define CLK_DPU_SPIBUS		58
+#define CLK_DPU_SPI_ACLK	59
+#define CLK_V2D			60
+#define CLK_EMMC_BUS		61
+
+#endif /* _DT_BINDINGS_SPACEMIT_CCU_H_ */
-- 
2.48.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll
  2025-03-06 17:57 [PATCH v5 0/5] Add clock controller support for SpacemiT K1 Haylen Chu
  2025-03-06 17:57 ` [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu
@ 2025-03-06 17:57 ` Haylen Chu
  2025-03-07  0:29   ` Yixun Lan
  2025-03-07  8:19   ` Krzysztof Kozlowski
  2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
                   ` (2 subsequent siblings)
  4 siblings, 2 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-06 17:57 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu

Add definition for the PLL found on Spacemit K1 SoC, which takes the
external 24MHz oscillator as input and generates clocks in various
frequencies for the system.

Signed-off-by: Haylen Chu <heylenay@4d2.org>
---
 .../bindings/clock/spacemit,k1-pll.yaml       | 50 +++++++++++++++++++
 include/dt-bindings/clock/spacemit,k1-ccu.h   | 37 ++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml

diff --git a/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml b/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
new file mode 100644
index 000000000000..23d7aa1bc573
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/spacemit,k1-pll.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Spacemit K1 PLL
+
+maintainers:
+  - Haylen Chu <heylenay@4d2.org>
+
+properties:
+  compatible:
+    const: spacemit,k1-pll
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    description: External 24MHz oscillator
+
+  spacemit,mpmu:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the "Main PMU (MPMU)" syscon. It is used to check PLL
+      lock status.
+
+  "#clock-cells":
+    const: 1
+    description:
+      See <dt-bindings/clock/spacemit,k1-ccu.h> for valid indices.
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - spacemit,mpmu
+  - "#clock-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    clock-controller@d4090000 {
+        compatible = "spacemit,k1-pll";
+        reg = <0xd4090000 0x1000>;
+        clocks = <&vctcxo_24m>;
+        spacemit,mpmu = <&sysctl_mpmu>;
+        #clock-cells = <1>;
+    };
diff --git a/include/dt-bindings/clock/spacemit,k1-ccu.h b/include/dt-bindings/clock/spacemit,k1-ccu.h
index 1f8b01db44ca..4a0c7163257e 100644
--- a/include/dt-bindings/clock/spacemit,k1-ccu.h
+++ b/include/dt-bindings/clock/spacemit,k1-ccu.h
@@ -6,6 +6,43 @@
 #ifndef _DT_BINDINGS_SPACEMIT_CCU_H_
 #define _DT_BINDINGS_SPACEMIT_CCU_H_
 
+/*	APBS (PLL) clocks	*/
+#define CLK_PLL1		0
+#define CLK_PLL2		1
+#define CLK_PLL3		2
+#define CLK_PLL1_D2		3
+#define CLK_PLL1_D3		4
+#define CLK_PLL1_D4		5
+#define CLK_PLL1_D5		6
+#define CLK_PLL1_D6		7
+#define CLK_PLL1_D7		8
+#define CLK_PLL1_D8		9
+#define CLK_PLL1_D11		10
+#define CLK_PLL1_D13		11
+#define CLK_PLL1_D23		12
+#define CLK_PLL1_D64		13
+#define CLK_PLL1_D10_AUD	14
+#define CLK_PLL1_D100_AUD	15
+#define CLK_PLL2_D1		16
+#define CLK_PLL2_D2		17
+#define CLK_PLL2_D3		18
+#define CLK_PLL2_D4		19
+#define CLK_PLL2_D5		20
+#define CLK_PLL2_D6		21
+#define CLK_PLL2_D7		22
+#define CLK_PLL2_D8		23
+#define CLK_PLL3_D1		24
+#define CLK_PLL3_D2		25
+#define CLK_PLL3_D3		26
+#define CLK_PLL3_D4		27
+#define CLK_PLL3_D5		28
+#define CLK_PLL3_D6		29
+#define CLK_PLL3_D7		30
+#define CLK_PLL3_D8		31
+#define CLK_PLL3_80		32
+#define CLK_PLL3_40		33
+#define CLK_PLL3_20		34
+
 /*	MPMU clocks	*/
 #define CLK_PLL1_307P2		0
 #define CLK_PLL1_76P8		1
-- 
2.48.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-06 17:57 [PATCH v5 0/5] Add clock controller support for SpacemiT K1 Haylen Chu
  2025-03-06 17:57 ` [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu
  2025-03-06 17:57 ` [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll Haylen Chu
@ 2025-03-06 17:57 ` Haylen Chu
  2025-03-07  0:51   ` Yixun Lan
                     ` (3 more replies)
  2025-03-06 17:57 ` [PATCH v5 4/5] clk: spacemit: k1: Add TWSI8 bus and function clocks Haylen Chu
  2025-03-06 17:57 ` [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1 Haylen Chu
  4 siblings, 4 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-06 17:57 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu

The clock tree of K1 SoC contains three main types of clock hardware
(PLL/DDN/MIX) and has control registers split into several multifunction
devices: APBS (PLLs), MPMU, APBC and APMU.

All register operations are done through regmap to ensure atomiciy
between concurrent operations of clock driver and reset,
power-domain driver that will be introduced in the future.

Signed-off-by: Haylen Chu <heylenay@4d2.org>
---
 drivers/clk/Kconfig               |    1 +
 drivers/clk/Makefile              |    1 +
 drivers/clk/spacemit/Kconfig      |   20 +
 drivers/clk/spacemit/Makefile     |    5 +
 drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
 drivers/clk/spacemit/ccu_common.h |   47 +
 drivers/clk/spacemit/ccu_ddn.c    |   80 ++
 drivers/clk/spacemit/ccu_ddn.h    |   48 +
 drivers/clk/spacemit/ccu_mix.c    |  284 +++++
 drivers/clk/spacemit/ccu_mix.h    |  246 +++++
 drivers/clk/spacemit/ccu_pll.c    |  146 +++
 drivers/clk/spacemit/ccu_pll.h    |   76 ++
 12 files changed, 2668 insertions(+)
 create mode 100644 drivers/clk/spacemit/Kconfig
 create mode 100644 drivers/clk/spacemit/Makefile
 create mode 100644 drivers/clk/spacemit/ccu-k1.c
 create mode 100644 drivers/clk/spacemit/ccu_common.h
 create mode 100644 drivers/clk/spacemit/ccu_ddn.c
 create mode 100644 drivers/clk/spacemit/ccu_ddn.h
 create mode 100644 drivers/clk/spacemit/ccu_mix.c
 create mode 100644 drivers/clk/spacemit/ccu_mix.h
 create mode 100644 drivers/clk/spacemit/ccu_pll.c
 create mode 100644 drivers/clk/spacemit/ccu_pll.h

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 713573b6c86c..19c1ed280fd7 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
 source "drivers/clk/sifive/Kconfig"
 source "drivers/clk/socfpga/Kconfig"
 source "drivers/clk/sophgo/Kconfig"
+source "drivers/clk/spacemit/Kconfig"
 source "drivers/clk/sprd/Kconfig"
 source "drivers/clk/starfive/Kconfig"
 source "drivers/clk/sunxi/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index bf4bd45adc3a..42867cd37c33 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
 obj-$(CONFIG_CLK_SIFIVE)		+= sifive/
 obj-y					+= socfpga/
 obj-y					+= sophgo/
+obj-y					+= spacemit/
 obj-$(CONFIG_PLAT_SPEAR)		+= spear/
 obj-y					+= sprd/
 obj-$(CONFIG_ARCH_STI)			+= st/
diff --git a/drivers/clk/spacemit/Kconfig b/drivers/clk/spacemit/Kconfig
new file mode 100644
index 000000000000..76090cd85668
--- /dev/null
+++ b/drivers/clk/spacemit/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SPACEMIT_CCU
+	tristate "Clock support for Spacemit SoCs"
+	default y
+	depends on ARCH_SPACEMIT || COMPILE_TEST
+	select MFD_SYSCON
+	help
+	  Say Y to enable clock controller unit support for Spacemit SoCs.
+
+if SPACEMIT_CCU
+
+config SPACEMIT_K1_CCU
+	tristate "Support for Spacemit K1 SoC"
+	default y
+	depends on ARCH_SPACEMIT || COMPILE_TEST
+	help
+	  Support for clock controller unit in Spacemit K1 SoC.
+
+endif
diff --git a/drivers/clk/spacemit/Makefile b/drivers/clk/spacemit/Makefile
new file mode 100644
index 000000000000..5ec6da61db98
--- /dev/null
+++ b/drivers/clk/spacemit/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_SPACEMIT_K1_CCU)	= spacemit-ccu-k1.o
+spacemit-ccu-k1-y		= ccu_pll.o ccu_mix.o ccu_ddn.o
+spacemit-ccu-k1-y		+= ccu-k1.o
diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
new file mode 100644
index 000000000000..5974a0a1b5f6
--- /dev/null
+++ b/drivers/clk/spacemit/ccu-k1.c
@@ -0,0 +1,1714 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "ccu_common.h"
+#include "ccu_pll.h"
+#include "ccu_mix.h"
+#include "ccu_ddn.h"
+
+#include <dt-bindings/clock/spacemit,k1-ccu.h>
+
+/*	APBS register offset	*/
+#define APBS_PLL1_SWCR1			0x100
+#define APBS_PLL1_SWCR2			0x104
+#define APBS_PLL1_SWCR3			0x108
+#define APBS_PLL2_SWCR1			0x118
+#define APBS_PLL2_SWCR2			0x11c
+#define APBS_PLL2_SWCR3			0x120
+#define APBS_PLL3_SWCR1			0x124
+#define APBS_PLL3_SWCR2			0x128
+#define APBS_PLL3_SWCR3			0x12c
+
+/* MPMU register offset */
+#define MPMU_POSR			0x10
+#define POSR_PLL1_LOCK			BIT(27)
+#define POSR_PLL2_LOCK			BIT(28)
+#define POSR_PLL3_LOCK			BIT(29)
+
+#define MPMU_WDTPCR			0x200
+#define MPMU_RIPCCR			0x210
+#define MPMU_ACGR			0x1024
+#define MPMU_SUCCR			0x14
+#define MPMU_ISCCR			0x44
+#define MPMU_SUCCR_1			0x10b0
+#define MPMU_APBCSCR			0x1050
+
+/* APBC register offset */
+#define APBC_UART1_CLK_RST		0x0
+#define APBC_UART2_CLK_RST		0x4
+#define APBC_GPIO_CLK_RST		0x8
+#define APBC_PWM0_CLK_RST		0xc
+#define APBC_PWM1_CLK_RST		0x10
+#define APBC_PWM2_CLK_RST		0x14
+#define APBC_PWM3_CLK_RST		0x18
+#define APBC_TWSI8_CLK_RST		0x20
+#define APBC_UART3_CLK_RST		0x24
+#define APBC_RTC_CLK_RST		0x28
+#define APBC_TWSI0_CLK_RST		0x2c
+#define APBC_TWSI1_CLK_RST		0x30
+#define APBC_TIMERS1_CLK_RST		0x34
+#define APBC_TWSI2_CLK_RST		0x38
+#define APBC_AIB_CLK_RST		0x3c
+#define APBC_TWSI4_CLK_RST		0x40
+#define APBC_TIMERS2_CLK_RST		0x44
+#define APBC_ONEWIRE_CLK_RST		0x48
+#define APBC_TWSI5_CLK_RST		0x4c
+#define APBC_DRO_CLK_RST		0x58
+#define APBC_IR_CLK_RST			0x5c
+#define APBC_TWSI6_CLK_RST		0x60
+#define APBC_COUNTER_CLK_SEL		0x64
+#define APBC_TWSI7_CLK_RST		0x68
+#define APBC_TSEN_CLK_RST		0x6c
+#define APBC_UART4_CLK_RST		0x70
+#define APBC_UART5_CLK_RST		0x74
+#define APBC_UART6_CLK_RST		0x78
+#define APBC_SSP3_CLK_RST		0x7c
+#define APBC_SSPA0_CLK_RST		0x80
+#define APBC_SSPA1_CLK_RST		0x84
+#define APBC_IPC_AP2AUD_CLK_RST		0x90
+#define APBC_UART7_CLK_RST		0x94
+#define APBC_UART8_CLK_RST		0x98
+#define APBC_UART9_CLK_RST		0x9c
+#define APBC_CAN0_CLK_RST		0xa0
+#define APBC_PWM4_CLK_RST		0xa8
+#define APBC_PWM5_CLK_RST		0xac
+#define APBC_PWM6_CLK_RST		0xb0
+#define APBC_PWM7_CLK_RST		0xb4
+#define APBC_PWM8_CLK_RST		0xb8
+#define APBC_PWM9_CLK_RST		0xbc
+#define APBC_PWM10_CLK_RST		0xc0
+#define APBC_PWM11_CLK_RST		0xc4
+#define APBC_PWM12_CLK_RST		0xc8
+#define APBC_PWM13_CLK_RST		0xcc
+#define APBC_PWM14_CLK_RST		0xd0
+#define APBC_PWM15_CLK_RST		0xd4
+#define APBC_PWM16_CLK_RST		0xd8
+#define APBC_PWM17_CLK_RST		0xdc
+#define APBC_PWM18_CLK_RST		0xe0
+#define APBC_PWM19_CLK_RST		0xe4
+
+/* APMU register offset */
+#define APMU_JPG_CLK_RES_CTRL		0x20
+#define APMU_CSI_CCIC2_CLK_RES_CTRL	0x24
+#define APMU_ISP_CLK_RES_CTRL		0x38
+#define APMU_LCD_CLK_RES_CTRL1		0x44
+#define APMU_LCD_SPI_CLK_RES_CTRL	0x48
+#define APMU_LCD_CLK_RES_CTRL2		0x4c
+#define APMU_CCIC_CLK_RES_CTRL		0x50
+#define APMU_SDH0_CLK_RES_CTRL		0x54
+#define APMU_SDH1_CLK_RES_CTRL		0x58
+#define APMU_USB_CLK_RES_CTRL		0x5c
+#define APMU_QSPI_CLK_RES_CTRL		0x60
+#define APMU_DMA_CLK_RES_CTRL		0x64
+#define APMU_AES_CLK_RES_CTRL		0x68
+#define APMU_VPU_CLK_RES_CTRL		0xa4
+#define APMU_GPU_CLK_RES_CTRL		0xcc
+#define APMU_SDH2_CLK_RES_CTRL		0xe0
+#define APMU_PMUA_MC_CTRL		0xe8
+#define APMU_PMU_CC2_AP			0x100
+#define APMU_PMUA_EM_CLK_RES_CTRL	0x104
+#define APMU_AUDIO_CLK_RES_CTRL		0x14c
+#define APMU_HDMI_CLK_RES_CTRL		0x1b8
+#define APMU_CCI550_CLK_CTRL		0x300
+#define APMU_ACLK_CLK_CTRL		0x388
+#define APMU_CPU_C0_CLK_CTRL		0x38C
+#define APMU_CPU_C1_CLK_CTRL		0x390
+#define APMU_PCIE_CLK_RES_CTRL_0	0x3cc
+#define APMU_PCIE_CLK_RES_CTRL_1	0x3d4
+#define APMU_PCIE_CLK_RES_CTRL_2	0x3dc
+#define APMU_EMAC0_CLK_RES_CTRL		0x3e4
+#define APMU_EMAC1_CLK_RES_CTRL		0x3ec
+
+/*	APBS clocks start	*/
+
+/* Frequency of pll{1,2} should not be updated at runtime */
+static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = {
+	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
+};
+
+static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = {
+	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
+};
+
+static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = {
+	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
+	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
+	CCU_PLL_RATE(3200000000UL, 0x0050dd67, 0x43eaaaab),
+};
+
+static CCU_PLL_DEFINE(pll1, pll1_rate_tbl,
+		      APBS_PLL1_SWCR1, APBS_PLL1_SWCR3,
+		      MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE);
+static CCU_PLL_DEFINE(pll2, pll2_rate_tbl,
+		      APBS_PLL2_SWCR1, APBS_PLL2_SWCR3,
+		      MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE);
+static CCU_PLL_DEFINE(pll3, pll3_rate_tbl,
+		      APBS_PLL3_SWCR1, APBS_PLL2_SWCR3,
+		      MPMU_POSR, POSR_PLL3_LOCK, CLK_SET_RATE_GATE);
+
+static CCU_GATE_FACTOR_DEFINE(pll1_d2, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(1), 2, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d3, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(2), 3, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d4, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(3), 4, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d5, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(4), 5, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d6, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(5), 6, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d7, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(6), 7, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d8, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(7), 8, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d11_223p4, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(15), 11, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d13_189, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(16), 13, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d23_106p8, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(20), 23, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d64_38p4, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(0), 64, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_aud_245p7, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(10), 10, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_aud_24p5, CCU_PARENT_HW(pll1),
+			      APBS_PLL1_SWCR2,
+			      BIT(11), 100, 1, 0);
+
+static CCU_GATE_FACTOR_DEFINE(pll2_d1, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(0), 1, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d2, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(1), 2, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d3, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(2), 3, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d4, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(3), 4, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d5, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(4), 5, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d6, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(5), 6, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d7, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(6), 7, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll2_d8, CCU_PARENT_HW(pll2),
+			      APBS_PLL2_SWCR2,
+			      BIT(7), 8, 1, 0);
+
+static CCU_GATE_FACTOR_DEFINE(pll3_d1, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(0), 1, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d2, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(1), 2, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d3, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(2), 3, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d4, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(3), 4, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d5, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(4), 5, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d6, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(5), 6, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d7, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(6), 7, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll3_d8, CCU_PARENT_HW(pll3),
+			      APBS_PLL3_SWCR2,
+			      BIT(7), 8, 1, 0);
+
+static CCU_FACTOR_DEFINE(pll3_20, CCU_PARENT_HW(pll3_d8), 20, 1);
+static CCU_FACTOR_DEFINE(pll3_40, CCU_PARENT_HW(pll3_d8), 10, 1);
+static CCU_FACTOR_DEFINE(pll3_80, CCU_PARENT_HW(pll3_d8), 5, 1);
+
+/*	APBS clocks end		*/
+
+/*	MPMU clocks start	*/
+static CCU_GATE_DEFINE(pll1_d8_307p2, CCU_PARENT_HW(pll1_d8),
+		       MPMU_ACGR,
+		       BIT(13), 0);
+static CCU_FACTOR_DEFINE(pll1_d32_76p8, CCU_PARENT_HW(pll1_d8_307p2),
+			 4, 1);
+static CCU_FACTOR_DEFINE(pll1_d40_61p44, CCU_PARENT_HW(pll1_d8_307p2),
+			 5, 1);
+static CCU_FACTOR_DEFINE(pll1_d16_153p6, CCU_PARENT_HW(pll1_d8),
+			 2, 1);
+static CCU_GATE_FACTOR_DEFINE(pll1_d24_102p4, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(12), 3, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(7), 6, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2_ap, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(11), 6, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_m3d128_57p6, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(8), 16, 3, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d96_25p6, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(4), 12, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(3), 24, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8_wdt, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(19), 24, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d384_6p4, CCU_PARENT_HW(pll1_d8),
+			      MPMU_ACGR,
+			      BIT(2), 48, 1, 0);
+static CCU_FACTOR_DEFINE(pll1_d768_3p2, CCU_PARENT_HW(pll1_d384_6p4),
+			 2, 1);
+static CCU_FACTOR_DEFINE(pll1_d1536_1p6, CCU_PARENT_HW(pll1_d384_6p4),
+			 4, 1);
+static CCU_FACTOR_DEFINE(pll1_d3072_0p8, CCU_PARENT_HW(pll1_d384_6p4),
+			 8, 1);
+
+static CCU_GATE_DEFINE(pll1_d6_409p6, CCU_PARENT_HW(pll1_d6),
+		       MPMU_ACGR,
+		       BIT(0), 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d12_204p8, CCU_PARENT_HW(pll1_d6),
+			      MPMU_ACGR,
+			      BIT(5), 2, 1, 0);
+
+static CCU_GATE_DEFINE(pll1_d5_491p52, CCU_PARENT_HW(pll1_d5),
+		       MPMU_ACGR,
+		       BIT(21), 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d10_245p76, CCU_PARENT_HW(pll1_d5),
+			      MPMU_ACGR,
+			      BIT(18), 2, 1, 0);
+
+static CCU_GATE_DEFINE(pll1_d4_614p4, CCU_PARENT_HW(pll1_d4),
+		       MPMU_ACGR,
+		       BIT(15), 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d52_47p26, CCU_PARENT_HW(pll1_d4),
+			      MPMU_ACGR,
+			      BIT(10), 13, 1, 0);
+static CCU_GATE_FACTOR_DEFINE(pll1_d78_31p5, CCU_PARENT_HW(pll1_d4),
+			      MPMU_ACGR,
+			      BIT(6), 39, 2, 0);
+
+static CCU_GATE_DEFINE(pll1_d3_819p2, CCU_PARENT_HW(pll1_d3),
+		       MPMU_ACGR,
+		       BIT(14), 0);
+
+static CCU_GATE_DEFINE(pll1_d2_1228p8, CCU_PARENT_HW(pll1_d2),
+		       MPMU_ACGR,
+		       BIT(16), 0);
+
+static CCU_GATE_DEFINE(slow_uart, CCU_PARENT_NAME(osc),
+		       MPMU_ACGR,
+		       BIT(1), CLK_IGNORE_UNUSED);
+static CCU_DDN_DEFINE(slow_uart1_14p74, pll1_d16_153p6,
+		      MPMU_SUCCR,
+		      GENMASK(28, 16), 16, GENMASK(12, 0), 0,
+		      0);
+static CCU_DDN_DEFINE(slow_uart2_48, pll1_d4_614p4,
+		      MPMU_SUCCR_1,
+		      GENMASK(28, 16), 16, GENMASK(12, 0), 0,
+		      0);
+
+static CCU_GATE_DEFINE(wdt_clk, CCU_PARENT_HW(pll1_d96_25p6),
+		       MPMU_WDTPCR,
+		       BIT(1),
+		       0);
+
+static CCU_GATE_FACTOR_DEFINE(i2s_sysclk, CCU_PARENT_HW(pll1_d16_153p6),
+			      MPMU_ISCCR,
+			      BIT(31), 50, 1,
+			      0);
+static CCU_GATE_FACTOR_DEFINE(i2s_bclk, CCU_PARENT_HW(i2s_sysclk),
+			      MPMU_ISCCR,
+			      BIT(29), 1, 1,
+			      0);
+
+static const struct clk_parent_data apb_parents[] = {
+	CCU_PARENT_HW(pll1_d96_25p6),
+	CCU_PARENT_HW(pll1_d48_51p2),
+	CCU_PARENT_HW(pll1_d96_25p6),
+	CCU_PARENT_HW(pll1_d24_102p4),
+};
+static CCU_MUX_DEFINE(apb_clk, apb_parents,
+		      MPMU_APBCSCR,
+		      0, 2,
+		      0);
+
+static CCU_GATE_DEFINE(wdt_bus_clk, CCU_PARENT_HW(apb_clk),
+		       MPMU_WDTPCR,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(ripc_clk, CCU_PARENT_HW(apb_clk),
+		       MPMU_RIPCCR,
+		       0x1,
+		       0);
+/*	MPMU clocks end		*/
+
+/*	APBC clocks start	*/
+static const struct clk_parent_data uart_clk_parents[] = {
+	CCU_PARENT_HW(pll1_m3d128_57p6),
+	CCU_PARENT_HW(slow_uart1_14p74),
+	CCU_PARENT_HW(slow_uart2_48),
+};
+static CCU_MUX_GATE_DEFINE(uart0_clk, uart_clk_parents,
+			   APBC_UART1_CLK_RST,
+			   4, 3, BIT(1),
+			   CLK_IS_CRITICAL);
+static CCU_MUX_GATE_DEFINE(uart2_clk, uart_clk_parents,
+			   APBC_UART2_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart3_clk, uart_clk_parents,
+			   APBC_UART3_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart4_clk, uart_clk_parents,
+			   APBC_UART4_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart5_clk, uart_clk_parents,
+			   APBC_UART5_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart6_clk, uart_clk_parents,
+			   APBC_UART6_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart7_clk, uart_clk_parents,
+			   APBC_UART7_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart8_clk, uart_clk_parents,
+			   APBC_UART8_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(uart9_clk, uart_clk_parents,
+			   APBC_UART9_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+
+static CCU_GATE_DEFINE(gpio_clk, CCU_PARENT_NAME(vctcxo_24m),
+		       APBC_GPIO_CLK_RST,
+		       BIT(1),
+		       0);
+
+static const struct clk_parent_data pwm_parents[] = {
+	CCU_PARENT_HW(pll1_d192_12p8),
+	CCU_PARENT_NAME(osc),
+};
+static CCU_MUX_GATE_DEFINE(pwm0_clk, pwm_parents,
+			   APBC_PWM0_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm1_clk, pwm_parents,
+			   APBC_PWM1_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm2_clk, pwm_parents,
+			   APBC_PWM2_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm3_clk, pwm_parents,
+			   APBC_PWM3_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm4_clk, pwm_parents,
+			   APBC_PWM4_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm5_clk, pwm_parents,
+			   APBC_PWM5_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm6_clk, pwm_parents,
+			   APBC_PWM6_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm7_clk, pwm_parents,
+			   APBC_PWM7_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm8_clk, pwm_parents,
+			   APBC_PWM8_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm9_clk, pwm_parents,
+			   APBC_PWM9_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm10_clk, pwm_parents,
+			   APBC_PWM10_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm11_clk, pwm_parents,
+			   APBC_PWM11_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm12_clk, pwm_parents,
+			   APBC_PWM12_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm13_clk, pwm_parents,
+			   APBC_PWM13_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm14_clk, pwm_parents,
+			   APBC_PWM14_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm15_clk, pwm_parents,
+			   APBC_PWM15_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm16_clk, pwm_parents,
+			   APBC_PWM16_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm17_clk, pwm_parents,
+			   APBC_PWM17_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm18_clk, pwm_parents,
+			   APBC_PWM18_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(pwm19_clk, pwm_parents,
+			   APBC_PWM19_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+
+static const struct clk_parent_data ssp_parents[] = {
+	CCU_PARENT_HW(pll1_d384_6p4),
+	CCU_PARENT_HW(pll1_d192_12p8),
+	CCU_PARENT_HW(pll1_d96_25p6),
+	CCU_PARENT_HW(pll1_d48_51p2),
+	CCU_PARENT_HW(pll1_d768_3p2),
+	CCU_PARENT_HW(pll1_d1536_1p6),
+	CCU_PARENT_HW(pll1_d3072_0p8),
+};
+static CCU_MUX_GATE_DEFINE(ssp3_clk, ssp_parents,
+			   APBC_SSP3_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+
+static CCU_GATE_DEFINE(rtc_clk, CCU_PARENT_NAME(osc),
+		       APBC_RTC_CLK_RST,
+		       BIT(7) | BIT(1),
+		       0);
+
+static const struct clk_parent_data twsi_parents[] = {
+	CCU_PARENT_HW(pll1_d78_31p5),
+	CCU_PARENT_HW(pll1_d48_51p2),
+	CCU_PARENT_HW(pll1_d40_61p44),
+};
+static CCU_MUX_GATE_DEFINE(twsi0_clk, twsi_parents,
+			   APBC_TWSI0_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(twsi1_clk, twsi_parents,
+			   APBC_TWSI1_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(twsi2_clk, twsi_parents,
+			   APBC_TWSI2_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(twsi4_clk, twsi_parents,
+			   APBC_TWSI4_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(twsi5_clk, twsi_parents,
+			   APBC_TWSI5_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(twsi6_clk, twsi_parents,
+			   APBC_TWSI6_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(twsi7_clk, twsi_parents,
+			   APBC_TWSI7_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+
+static const struct clk_parent_data timer_parents[] = {
+	CCU_PARENT_HW(pll1_d192_12p8),
+	CCU_PARENT_NAME(osc),
+	CCU_PARENT_HW(pll1_d384_6p4),
+	CCU_PARENT_NAME(vctcxo_3m),
+	CCU_PARENT_NAME(vctcxo_1m),
+};
+static CCU_MUX_GATE_DEFINE(timers1_clk, timer_parents,
+			   APBC_TIMERS1_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(timers2_clk, timer_parents,
+			   APBC_TIMERS2_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+
+static CCU_GATE_DEFINE(aib_clk, CCU_PARENT_NAME(vctcxo_24m),
+		       APBC_AIB_CLK_RST,
+		       BIT(1),
+		       0);
+
+static CCU_GATE_DEFINE(onewire_clk, CCU_PARENT_NAME(vctcxo_24m),
+		       APBC_ONEWIRE_CLK_RST,
+		       BIT(1),
+		       0);
+
+static const struct clk_parent_data sspa_parents[] = {
+	CCU_PARENT_HW(pll1_d384_6p4),
+	CCU_PARENT_HW(pll1_d192_12p8),
+	CCU_PARENT_HW(pll1_d96_25p6),
+	CCU_PARENT_HW(pll1_d48_51p2),
+	CCU_PARENT_HW(pll1_d768_3p2),
+	CCU_PARENT_HW(pll1_d1536_1p6),
+	CCU_PARENT_HW(pll1_d3072_0p8),
+	CCU_PARENT_HW(i2s_bclk),
+};
+static CCU_MUX_GATE_DEFINE(sspa0_clk, sspa_parents,
+			   APBC_SSPA0_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_MUX_GATE_DEFINE(sspa1_clk, sspa_parents,
+			   APBC_SSPA1_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_GATE_DEFINE(dro_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_DRO_CLK_RST,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(ir_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_IR_CLK_RST,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(tsen_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TSEN_CLK_RST,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(ipc_ap2aud_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_IPC_AP2AUD_CLK_RST,
+		       BIT(1),
+		       0);
+
+static const struct clk_parent_data can_parents[] = {
+	CCU_PARENT_HW(pll3_20),
+	CCU_PARENT_HW(pll3_40),
+	CCU_PARENT_HW(pll3_80),
+};
+static CCU_MUX_GATE_DEFINE(can0_clk, can_parents,
+			   APBC_CAN0_CLK_RST,
+			   4, 3, BIT(1),
+			   0);
+static CCU_GATE_DEFINE(can0_bus_clk, CCU_PARENT_NAME(vctcxo_24m),
+		       APBC_CAN0_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(uart0_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART1_CLK_RST,
+		       BIT(0),
+		       CLK_IS_CRITICAL);
+static CCU_GATE_DEFINE(uart2_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART2_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart3_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART3_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart4_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART4_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart5_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART5_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart6_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART6_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart7_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART7_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart8_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART8_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(uart9_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_UART9_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(gpio_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_GPIO_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(pwm0_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM0_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm1_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM1_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm2_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM2_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm3_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM3_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm4_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM4_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm5_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM5_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm6_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM6_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm7_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM7_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm8_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM8_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm9_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM9_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm10_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM10_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm11_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM11_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm12_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM12_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm13_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM13_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm14_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM14_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm15_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM15_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm16_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM16_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm17_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM17_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm18_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM18_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(pwm19_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_PWM19_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(ssp3_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_SSP3_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(rtc_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_RTC_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(twsi0_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI0_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(twsi1_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI1_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(twsi2_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI2_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(twsi4_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI4_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(twsi5_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI5_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(twsi6_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI6_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(twsi7_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TWSI7_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(timers1_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TIMERS1_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(timers2_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TIMERS2_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(aib_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_AIB_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(onewire_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_ONEWIRE_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(sspa0_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_SSPA0_CLK_RST,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(sspa1_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_SSPA1_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(tsen_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_TSEN_CLK_RST,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(ipc_ap2aud_bus_clk, CCU_PARENT_HW(apb_clk),
+		       APBC_IPC_AP2AUD_CLK_RST,
+		       BIT(0),
+		       0);
+/*	APBC clocks end		*/
+
+/*	APMU clocks start	*/
+static const struct clk_parent_data pmua_aclk_parents[] = {
+	CCU_PARENT_HW(pll1_d10_245p76),
+	CCU_PARENT_HW(pll1_d8_307p2),
+};
+static CCU_DIV_FC_MUX_DEFINE(pmua_aclk, pmua_aclk_parents,
+			     APMU_ACLK_CLK_CTRL,
+			     1, 2, BIT(4),
+			     0, 1,
+			     0);
+
+static const struct clk_parent_data cci550_clk_parents[] = {
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll2_d3),
+};
+static CCU_DIV_FC_MUX_DEFINE(cci550_clk, cci550_clk_parents,
+			     APMU_CCI550_CLK_CTRL,
+			     8, 3, BIT(12), 0, 2, CLK_IS_CRITICAL);
+
+static const struct clk_parent_data cpu_c0_hi_clk_parents[] = {
+	CCU_PARENT_HW(pll3_d2),
+	CCU_PARENT_HW(pll3_d1),
+};
+static CCU_MUX_DEFINE(cpu_c0_hi_clk, cpu_c0_hi_clk_parents,
+		      APMU_CPU_C0_CLK_CTRL,
+		      13, 1, 0);
+static const struct clk_parent_data cpu_c0_clk_parents[] = {
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d2_1228p8),
+	CCU_PARENT_HW(pll3_d3),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(cpu_c0_hi_clk),
+};
+static CCU_MUX_FC_DEFINE(cpu_c0_core_clk, cpu_c0_clk_parents,
+			 APMU_CPU_C0_CLK_CTRL,
+			 BIT(12), 0, 3, CLK_IS_CRITICAL);
+static CCU_DIV_DEFINE(cpu_c0_ace_clk, CCU_PARENT_HW(cpu_c0_core_clk),
+		      APMU_CPU_C0_CLK_CTRL,
+		      6, 3, CLK_IS_CRITICAL);
+static CCU_DIV_DEFINE(cpu_c0_tcm_clk, CCU_PARENT_HW(cpu_c0_core_clk),
+		      APMU_CPU_C0_CLK_CTRL, 9, 3, CLK_IS_CRITICAL);
+
+static const struct clk_parent_data cpu_c1_hi_clk_parents[] = {
+	CCU_PARENT_HW(pll3_d2),
+	CCU_PARENT_HW(pll3_d1),
+};
+static CCU_MUX_DEFINE(cpu_c1_hi_clk, cpu_c1_hi_clk_parents,
+		      APMU_CPU_C1_CLK_CTRL,
+		      13, 1, CLK_IS_CRITICAL);
+static const struct clk_parent_data cpu_c1_clk_parents[] = {
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d2_1228p8),
+	CCU_PARENT_HW(pll3_d3),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(cpu_c1_hi_clk),
+};
+static CCU_MUX_FC_DEFINE(cpu_c1_core_clk, cpu_c1_clk_parents,
+			 APMU_CPU_C1_CLK_CTRL,
+			 BIT(12), 0, 3, CLK_IS_CRITICAL);
+static CCU_DIV_DEFINE(cpu_c1_ace_clk, CCU_PARENT_HW(cpu_c1_core_clk),
+		      APMU_CPU_C1_CLK_CTRL,
+		      6, 3, CLK_IS_CRITICAL);
+
+static const struct clk_parent_data jpg_parents[] = {
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll1_d2_1228p8),
+	CCU_PARENT_HW(pll2_d4),
+	CCU_PARENT_HW(pll2_d3),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(jpg_clk, jpg_parents,
+				  APMU_JPG_CLK_RES_CTRL,
+				  5, 3, BIT(15),
+				  2, 3, BIT(1),
+				  0);
+
+static const struct clk_parent_data ccic2phy_parents[] = {
+	CCU_PARENT_HW(pll1_d24_102p4),
+	CCU_PARENT_HW(pll1_d48_51p2_ap),
+};
+static CCU_MUX_GATE_DEFINE(ccic2phy_clk, ccic2phy_parents,
+			   APMU_CSI_CCIC2_CLK_RES_CTRL,
+			   7, 1, BIT(5),
+			   0);
+
+static const struct clk_parent_data ccic3phy_parents[] = {
+	CCU_PARENT_HW(pll1_d24_102p4),
+	CCU_PARENT_HW(pll1_d48_51p2_ap),
+};
+static CCU_MUX_GATE_DEFINE(ccic3phy_clk, ccic3phy_parents,
+			   APMU_CSI_CCIC2_CLK_RES_CTRL,
+			   31, 1, BIT(30),
+			   0);
+
+static const struct clk_parent_data csi_parents[] = {
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll2_d2),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(pll2_d4),
+	CCU_PARENT_HW(pll1_d2_1228p8),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(csi_clk, csi_parents,
+				  APMU_CSI_CCIC2_CLK_RES_CTRL,
+				  20, 3, BIT(15),
+				  16, 3, BIT(4),
+				  0);
+
+static const struct clk_parent_data camm_parents[] = {
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll2_d5),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_NAME(vctcxo_24m),
+};
+static CCU_DIV_MUX_GATE_DEFINE(camm0_clk, camm_parents,
+			       APMU_CSI_CCIC2_CLK_RES_CTRL,
+			       23, 4, 8, 2,
+			       BIT(28),
+			       0);
+static CCU_DIV_MUX_GATE_DEFINE(camm1_clk, camm_parents,
+			       APMU_CSI_CCIC2_CLK_RES_CTRL,
+			       23, 4, 8, 2, BIT(6),
+			       0);
+static CCU_DIV_MUX_GATE_DEFINE(camm2_clk, camm_parents,
+			       APMU_CSI_CCIC2_CLK_RES_CTRL,
+			       23, 4, 8, 2, BIT(3),
+			       0);
+
+static const struct clk_parent_data isp_cpp_parents[] = {
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+};
+static CCU_DIV_MUX_GATE_DEFINE(isp_cpp_clk, isp_cpp_parents,
+			       APMU_ISP_CLK_RES_CTRL,
+			       24, 2, 26, 1, BIT(28),
+			       0);
+static const struct clk_parent_data isp_bus_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll1_d10_245p76),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(isp_bus_clk, isp_bus_parents,
+				  APMU_ISP_CLK_RES_CTRL,
+				  18, 3, BIT(23),
+				  21, 2, BIT(17),
+				  0);
+static const struct clk_parent_data isp_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d8_307p2),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(isp_clk, isp_parents,
+				  APMU_ISP_CLK_RES_CTRL,
+				  4, 3, BIT(7),
+				  8, 2, BIT(1),
+				  0);
+
+static const struct clk_parent_data dpumclk_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d8_307p2),
+};
+static CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(dpu_mclk, dpumclk_parents,
+					APMU_LCD_CLK_RES_CTRL2,
+					APMU_LCD_CLK_RES_CTRL1,
+					1, 4, BIT(29),
+					5, 3, BIT(0),
+					0);
+
+static const struct clk_parent_data dpuesc_parents[] = {
+	CCU_PARENT_HW(pll1_d48_51p2_ap),
+	CCU_PARENT_HW(pll1_d52_47p26),
+	CCU_PARENT_HW(pll1_d96_25p6),
+	CCU_PARENT_HW(pll1_d32_76p8),
+};
+static CCU_MUX_GATE_DEFINE(dpu_esc_clk, dpuesc_parents,
+			   APMU_LCD_CLK_RES_CTRL1,
+			   0, 2, BIT(2),
+			   0);
+
+static const struct clk_parent_data dpubit_parents[] = {
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll2_d2),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(pll1_d2_1228p8),
+	CCU_PARENT_HW(pll2_d4),
+	CCU_PARENT_HW(pll2_d5),
+	CCU_PARENT_HW(pll2_d7),
+	CCU_PARENT_HW(pll2_d8),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, dpubit_parents,
+				  APMU_LCD_CLK_RES_CTRL1,
+				  17, 3, BIT(31),
+				  20, 3, BIT(16),
+				  0);
+
+static const struct clk_parent_data dpupx_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll2_d7),
+	CCU_PARENT_HW(pll2_d8),
+};
+static CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(dpu_pxclk, dpupx_parents,
+					APMU_LCD_CLK_RES_CTRL2,
+					APMU_LCD_CLK_RES_CTRL1,
+					17, 4, BIT(30),
+					21, 3, BIT(16),
+					0);
+
+static CCU_GATE_DEFINE(dpu_hclk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_LCD_CLK_RES_CTRL1,
+		       BIT(5),
+		       0);
+
+static const struct clk_parent_data dpu_spi_parents[] = {
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d10_245p76),
+	CCU_PARENT_HW(pll1_d11_223p4),
+	CCU_PARENT_HW(pll1_d13_189),
+	CCU_PARENT_HW(pll1_d23_106p8),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(pll2_d5),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_spi_clk, dpu_spi_parents,
+				  APMU_LCD_SPI_CLK_RES_CTRL,
+				  8, 3, BIT(7),
+				  12, 3, BIT(1),
+				  0);
+static CCU_GATE_DEFINE(dpu_spi_hbus_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_LCD_SPI_CLK_RES_CTRL,
+		       BIT(3),
+		       0);
+static CCU_GATE_DEFINE(dpu_spi_bus_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_LCD_SPI_CLK_RES_CTRL,
+		       BIT(5),
+		       0);
+static CCU_GATE_DEFINE(dpu_spi_aclk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_LCD_SPI_CLK_RES_CTRL,
+		       BIT(6),
+		       0);
+
+static const struct clk_parent_data v2d_parents[] = {
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll1_d4_614p4),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(v2d_clk, v2d_parents,
+				  APMU_LCD_CLK_RES_CTRL1,
+				  9, 3, BIT(28),
+				  12, 2, BIT(8),
+				  0);
+
+static const struct clk_parent_data ccic_4x_parents[] = {
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll2_d2),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(pll2_d4),
+	CCU_PARENT_HW(pll1_d2_1228p8),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(ccic_4x_clk, ccic_4x_parents,
+				  APMU_CCIC_CLK_RES_CTRL,
+				  18, 3, BIT(15),
+				  23, 2, BIT(4),
+				  0);
+
+static const struct clk_parent_data ccic1phy_parents[] = {
+	CCU_PARENT_HW(pll1_d24_102p4),
+	CCU_PARENT_HW(pll1_d48_51p2_ap),
+};
+static CCU_MUX_GATE_DEFINE(ccic1phy_clk, ccic1phy_parents,
+			   APMU_CCIC_CLK_RES_CTRL,
+			   7, 1, BIT(5),
+			   0);
+
+static CCU_GATE_DEFINE(sdh_axi_aclk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_SDH0_CLK_RES_CTRL,
+		       BIT(3),
+		       0);
+static const struct clk_parent_data sdh01_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll2_d8),
+	CCU_PARENT_HW(pll2_d5),
+	CCU_PARENT_HW(pll1_d11_223p4),
+	CCU_PARENT_HW(pll1_d13_189),
+	CCU_PARENT_HW(pll1_d23_106p8),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(sdh0_clk, sdh01_parents,
+				  APMU_SDH0_CLK_RES_CTRL,
+				  8, 3, BIT(11),
+				  5, 3, BIT(4),
+				  0);
+static CCU_DIV_FC_MUX_GATE_DEFINE(sdh1_clk, sdh01_parents,
+				  APMU_SDH1_CLK_RES_CTRL,
+				  8, 3, BIT(11),
+				  5, 3, BIT(4),
+				  0);
+static const struct clk_parent_data sdh2_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll2_d8),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll1_d11_223p4),
+	CCU_PARENT_HW(pll1_d13_189),
+	CCU_PARENT_HW(pll1_d23_106p8),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(sdh2_clk, sdh2_parents,
+				  APMU_SDH2_CLK_RES_CTRL,
+				  8, 3, BIT(11),
+				  5, 3, BIT(4),
+				  0);
+
+static CCU_GATE_DEFINE(usb_axi_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_USB_CLK_RES_CTRL,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(usb_p1_aclk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_USB_CLK_RES_CTRL,
+		       BIT(5),
+		       0);
+static CCU_GATE_DEFINE(usb30_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_USB_CLK_RES_CTRL,
+		       BIT(8),
+		       0);
+
+static const struct clk_parent_data qspi_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll2_d8),
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll1_d10_245p76),
+	CCU_PARENT_HW(pll1_d11_223p4),
+	CCU_PARENT_HW(pll1_d23_106p8),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d13_189),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(qspi_clk, qspi_parents,
+				   APMU_QSPI_CLK_RES_CTRL,
+				   9, 3, BIT(12),
+				   6, 3, BIT(4),
+				   0);
+static CCU_GATE_DEFINE(qspi_bus_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_QSPI_CLK_RES_CTRL,
+		       BIT(3),
+		       0);
+static CCU_GATE_DEFINE(dma_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_DMA_CLK_RES_CTRL,
+		       BIT(3),
+		       0);
+
+static const struct clk_parent_data aes_parents[] = {
+	CCU_PARENT_HW(pll1_d12_204p8),
+	CCU_PARENT_HW(pll1_d24_102p4),
+};
+static CCU_MUX_GATE_DEFINE(aes_clk, aes_parents,
+			   APMU_AES_CLK_RES_CTRL,
+			   6, 1, BIT(5),
+			   0);
+
+static const struct clk_parent_data vpu_parents[] = {
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll3_d6),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(pll2_d4),
+	CCU_PARENT_HW(pll2_d5),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(vpu_clk, vpu_parents,
+				  APMU_VPU_CLK_RES_CTRL,
+				  13, 3, BIT(21),
+				  10, 3,
+				  BIT(3),
+				  0);
+
+static const struct clk_parent_data gpu_parents[] = {
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d3_819p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll3_d6),
+	CCU_PARENT_HW(pll2_d3),
+	CCU_PARENT_HW(pll2_d4),
+	CCU_PARENT_HW(pll2_d5),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(gpu_clk, gpu_parents,
+				  APMU_GPU_CLK_RES_CTRL,
+				  12, 3, BIT(15),
+				  18, 3,
+				  BIT(4),
+				  0);
+
+static const struct clk_parent_data emmc_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d52_47p26),
+	CCU_PARENT_HW(pll1_d3_819p2),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(emmc_clk, emmc_parents,
+				  APMU_PMUA_EM_CLK_RES_CTRL,
+				  8, 3, BIT(11),
+				  6, 2,
+				  BIT(4),
+				  0);
+static CCU_DIV_GATE_DEFINE(emmc_x_clk, CCU_PARENT_HW(pll1_d2_1228p8),
+			   APMU_PMUA_EM_CLK_RES_CTRL,
+			   12, 3, BIT(15),
+			   0);
+
+static const struct clk_parent_data audio_parents[] = {
+	CCU_PARENT_HW(pll1_aud_245p7),
+	CCU_PARENT_HW(pll1_d8_307p2),
+	CCU_PARENT_HW(pll1_d6_409p6),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(audio_clk, audio_parents,
+				  APMU_AUDIO_CLK_RES_CTRL,
+				  4, 3, BIT(15),
+				  7, 3,
+				  BIT(12),
+				  0);
+
+static const struct clk_parent_data hdmi_parents[] = {
+	CCU_PARENT_HW(pll1_d6_409p6),
+	CCU_PARENT_HW(pll1_d5_491p52),
+	CCU_PARENT_HW(pll1_d4_614p4),
+	CCU_PARENT_HW(pll1_d8_307p2),
+};
+static CCU_DIV_FC_MUX_GATE_DEFINE(hdmi_mclk, hdmi_parents,
+				  APMU_HDMI_CLK_RES_CTRL,
+				  1, 4, BIT(29),
+				  5, 3,
+				  BIT(0),
+				  0);
+
+static CCU_GATE_DEFINE(pcie0_master_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_0,
+		       BIT(2),
+		       0);
+static CCU_GATE_DEFINE(pcie0_slave_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_0,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(pcie0_dbi_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_0,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(pcie1_master_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_1,
+		       BIT(2),
+		       0);
+static CCU_GATE_DEFINE(pcie1_slave_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_1,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(pcie1_dbi_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_1,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(pcie2_master_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_2,
+		       BIT(2),
+		       0);
+static CCU_GATE_DEFINE(pcie2_slave_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_2,
+		       BIT(1),
+		       0);
+static CCU_GATE_DEFINE(pcie2_dbi_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PCIE_CLK_RES_CTRL_2,
+		       BIT(0),
+		       0);
+
+static CCU_GATE_DEFINE(emac0_bus_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_EMAC0_CLK_RES_CTRL,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(emac0_ptp_clk, CCU_PARENT_HW(pll2_d6),
+		       APMU_EMAC0_CLK_RES_CTRL,
+		       BIT(15),
+		       0);
+static CCU_GATE_DEFINE(emac1_bus_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_EMAC1_CLK_RES_CTRL,
+		       BIT(0),
+		       0);
+static CCU_GATE_DEFINE(emac1_ptp_clk, CCU_PARENT_HW(pll2_d6),
+		       APMU_EMAC1_CLK_RES_CTRL,
+		       BIT(15),
+		       0);
+
+static CCU_GATE_DEFINE(emmc_bus_clk, CCU_PARENT_HW(pmua_aclk),
+		       APMU_PMUA_EM_CLK_RES_CTRL,
+		       BIT(3),
+		       0);
+/*	APMU clocks end		*/
+
+struct spacemit_ccu_clk {
+	int id;
+	struct clk_hw *hw;
+};
+
+static struct spacemit_ccu_clk k1_ccu_apbs_clks[] = {
+	{ CLK_PLL1,		&pll1.common.hw },
+	{ CLK_PLL2,		&pll2.common.hw },
+	{ CLK_PLL3,		&pll3.common.hw },
+	{ CLK_PLL1_D2,		&pll1_d2.common.hw },
+	{ CLK_PLL1_D3,		&pll1_d3.common.hw },
+	{ CLK_PLL1_D4,		&pll1_d4.common.hw },
+	{ CLK_PLL1_D5,		&pll1_d5.common.hw },
+	{ CLK_PLL1_D6,		&pll1_d6.common.hw },
+	{ CLK_PLL1_D7,		&pll1_d7.common.hw },
+	{ CLK_PLL1_D8,		&pll1_d8.common.hw },
+	{ CLK_PLL1_D11,		&pll1_d11_223p4.common.hw },
+	{ CLK_PLL1_D13,		&pll1_d13_189.common.hw },
+	{ CLK_PLL1_D23,		&pll1_d23_106p8.common.hw },
+	{ CLK_PLL1_D64,		&pll1_d64_38p4.common.hw },
+	{ CLK_PLL1_D10_AUD,	&pll1_aud_245p7.common.hw },
+	{ CLK_PLL1_D100_AUD,	&pll1_aud_24p5.common.hw },
+	{ CLK_PLL2_D1,		&pll2_d1.common.hw },
+	{ CLK_PLL2_D2,		&pll2_d2.common.hw },
+	{ CLK_PLL2_D3,		&pll2_d3.common.hw },
+	{ CLK_PLL2_D4,		&pll2_d4.common.hw },
+	{ CLK_PLL2_D5,		&pll2_d5.common.hw },
+	{ CLK_PLL2_D6,		&pll2_d6.common.hw },
+	{ CLK_PLL2_D7,		&pll2_d7.common.hw },
+	{ CLK_PLL2_D8,		&pll2_d8.common.hw },
+	{ CLK_PLL3_D1,		&pll3_d1.common.hw },
+	{ CLK_PLL3_D2,		&pll3_d2.common.hw },
+	{ CLK_PLL3_D3,		&pll3_d3.common.hw },
+	{ CLK_PLL3_D4,		&pll3_d4.common.hw },
+	{ CLK_PLL3_D5,		&pll3_d5.common.hw },
+	{ CLK_PLL3_D6,		&pll3_d6.common.hw },
+	{ CLK_PLL3_D7,		&pll3_d7.common.hw },
+	{ CLK_PLL3_D8,		&pll3_d8.common.hw },
+	{ CLK_PLL3_80,		&pll3_80.common.hw },
+	{ CLK_PLL3_40,		&pll3_40.common.hw },
+	{ CLK_PLL3_20,		&pll3_20.common.hw },
+	{ 0,			NULL },
+};
+
+static struct spacemit_ccu_clk k1_ccu_mpmu_clks[] = {
+	{ CLK_PLL1_307P2,	&pll1_d8_307p2.common.hw },
+	{ CLK_PLL1_76P8,	&pll1_d32_76p8.common.hw },
+	{ CLK_PLL1_61P44,	&pll1_d40_61p44.common.hw },
+	{ CLK_PLL1_153P6,	&pll1_d16_153p6.common.hw },
+	{ CLK_PLL1_102P4,	&pll1_d24_102p4.common.hw },
+	{ CLK_PLL1_51P2,	&pll1_d48_51p2.common.hw },
+	{ CLK_PLL1_51P2_AP,	&pll1_d48_51p2_ap.common.hw },
+	{ CLK_PLL1_57P6,	&pll1_m3d128_57p6.common.hw },
+	{ CLK_PLL1_25P6,	&pll1_d96_25p6.common.hw },
+	{ CLK_PLL1_12P8,	&pll1_d192_12p8.common.hw },
+	{ CLK_PLL1_12P8_WDT,	&pll1_d192_12p8_wdt.common.hw },
+	{ CLK_PLL1_6P4,		&pll1_d384_6p4.common.hw },
+	{ CLK_PLL1_3P2,		&pll1_d768_3p2.common.hw },
+	{ CLK_PLL1_1P6,		&pll1_d1536_1p6.common.hw },
+	{ CLK_PLL1_0P8,		&pll1_d3072_0p8.common.hw },
+	{ CLK_PLL1_409P6,	&pll1_d6_409p6.common.hw },
+	{ CLK_PLL1_204P8,	&pll1_d12_204p8.common.hw },
+	{ CLK_PLL1_491,		&pll1_d5_491p52.common.hw },
+	{ CLK_PLL1_245P76,	&pll1_d10_245p76.common.hw },
+	{ CLK_PLL1_614,		&pll1_d4_614p4.common.hw },
+	{ CLK_PLL1_47P26,	&pll1_d52_47p26.common.hw },
+	{ CLK_PLL1_31P5,	&pll1_d78_31p5.common.hw },
+	{ CLK_PLL1_819,		&pll1_d3_819p2.common.hw },
+	{ CLK_PLL1_1228,	&pll1_d2_1228p8.common.hw },
+	{ CLK_SLOW_UART,	&slow_uart.common.hw },
+	{ CLK_SLOW_UART1,	&slow_uart1_14p74.common.hw },
+	{ CLK_SLOW_UART2,	&slow_uart2_48.common.hw },
+	{ CLK_WDT,		&wdt_clk.common.hw },
+	{ CLK_RIPC,		&ripc_clk.common.hw },
+	{ CLK_I2S_SYSCLK,	&i2s_sysclk.common.hw },
+	{ CLK_I2S_BCLK,		&i2s_bclk.common.hw },
+	{ CLK_APB,		&apb_clk.common.hw },
+	{ CLK_WDT_BUS,		&wdt_bus_clk.common.hw },
+	{ 0,			NULL },
+};
+
+static struct spacemit_ccu_clk k1_ccu_apbc_clks[] = {
+	{ CLK_UART0,		&uart0_clk.common.hw },
+	{ CLK_UART2,		&uart2_clk.common.hw },
+	{ CLK_UART3,		&uart3_clk.common.hw },
+	{ CLK_UART4,		&uart4_clk.common.hw },
+	{ CLK_UART5,		&uart5_clk.common.hw },
+	{ CLK_UART6,		&uart6_clk.common.hw },
+	{ CLK_UART7,		&uart7_clk.common.hw },
+	{ CLK_UART8,		&uart8_clk.common.hw },
+	{ CLK_UART9,		&uart9_clk.common.hw },
+	{ CLK_GPIO,		&gpio_clk.common.hw },
+	{ CLK_PWM0,		&pwm0_clk.common.hw },
+	{ CLK_PWM1,		&pwm1_clk.common.hw },
+	{ CLK_PWM2,		&pwm2_clk.common.hw },
+	{ CLK_PWM3,		&pwm3_clk.common.hw },
+	{ CLK_PWM4,		&pwm4_clk.common.hw },
+	{ CLK_PWM5,		&pwm5_clk.common.hw },
+	{ CLK_PWM6,		&pwm6_clk.common.hw },
+	{ CLK_PWM7,		&pwm7_clk.common.hw },
+	{ CLK_PWM8,		&pwm8_clk.common.hw },
+	{ CLK_PWM9,		&pwm9_clk.common.hw },
+	{ CLK_PWM10,		&pwm10_clk.common.hw },
+	{ CLK_PWM11,		&pwm11_clk.common.hw },
+	{ CLK_PWM12,		&pwm12_clk.common.hw },
+	{ CLK_PWM13,		&pwm13_clk.common.hw },
+	{ CLK_PWM14,		&pwm14_clk.common.hw },
+	{ CLK_PWM15,		&pwm15_clk.common.hw },
+	{ CLK_PWM16,		&pwm16_clk.common.hw },
+	{ CLK_PWM17,		&pwm17_clk.common.hw },
+	{ CLK_PWM18,		&pwm18_clk.common.hw },
+	{ CLK_PWM19,		&pwm19_clk.common.hw },
+	{ CLK_SSP3,		&ssp3_clk.common.hw },
+	{ CLK_RTC,		&rtc_clk.common.hw },
+	{ CLK_TWSI0,		&twsi0_clk.common.hw },
+	{ CLK_TWSI1,		&twsi1_clk.common.hw },
+	{ CLK_TWSI2,		&twsi2_clk.common.hw },
+	{ CLK_TWSI4,		&twsi4_clk.common.hw },
+	{ CLK_TWSI5,		&twsi5_clk.common.hw },
+	{ CLK_TWSI6,		&twsi6_clk.common.hw },
+	{ CLK_TWSI7,		&twsi7_clk.common.hw },
+	{ CLK_TWSI8,		&twsi8_clk.common.hw },
+	{ CLK_TIMERS1,		&timers1_clk.common.hw },
+	{ CLK_TIMERS2,		&timers2_clk.common.hw },
+	{ CLK_AIB,		&aib_clk.common.hw },
+	{ CLK_ONEWIRE,		&onewire_clk.common.hw },
+	{ CLK_SSPA0,		&sspa0_clk.common.hw },
+	{ CLK_SSPA1,		&sspa1_clk.common.hw },
+	{ CLK_DRO,		&dro_clk.common.hw },
+	{ CLK_IR,		&ir_clk.common.hw },
+	{ CLK_TSEN,		&tsen_clk.common.hw },
+	{ CLK_IPC_AP2AUD,	&ipc_ap2aud_clk.common.hw },
+	{ CLK_CAN0,		&can0_clk.common.hw },
+	{ CLK_CAN0_BUS,		&can0_bus_clk.common.hw },
+	{ CLK_UART0_BUS,	&uart0_bus_clk.common.hw },
+	{ CLK_UART2_BUS,	&uart2_bus_clk.common.hw },
+	{ CLK_UART3_BUS,	&uart3_bus_clk.common.hw },
+	{ CLK_UART4_BUS,	&uart4_bus_clk.common.hw },
+	{ CLK_UART5_BUS,	&uart5_bus_clk.common.hw },
+	{ CLK_UART6_BUS,	&uart6_bus_clk.common.hw },
+	{ CLK_UART7_BUS,	&uart7_bus_clk.common.hw },
+	{ CLK_UART8_BUS,	&uart8_bus_clk.common.hw },
+	{ CLK_UART9_BUS,	&uart9_bus_clk.common.hw },
+	{ CLK_GPIO_BUS,		&gpio_bus_clk.common.hw },
+	{ CLK_PWM0_BUS,		&pwm0_bus_clk.common.hw },
+	{ CLK_PWM1_BUS,		&pwm1_bus_clk.common.hw },
+	{ CLK_PWM2_BUS,		&pwm2_bus_clk.common.hw },
+	{ CLK_PWM3_BUS,		&pwm3_bus_clk.common.hw },
+	{ CLK_PWM4_BUS,		&pwm4_bus_clk.common.hw },
+	{ CLK_PWM5_BUS,		&pwm5_bus_clk.common.hw },
+	{ CLK_PWM6_BUS,		&pwm6_bus_clk.common.hw },
+	{ CLK_PWM7_BUS,		&pwm7_bus_clk.common.hw },
+	{ CLK_PWM8_BUS,		&pwm8_bus_clk.common.hw },
+	{ CLK_PWM9_BUS,		&pwm9_bus_clk.common.hw },
+	{ CLK_PWM10_BUS,	&pwm10_bus_clk.common.hw },
+	{ CLK_PWM11_BUS,	&pwm11_bus_clk.common.hw },
+	{ CLK_PWM12_BUS,	&pwm12_bus_clk.common.hw },
+	{ CLK_PWM13_BUS,	&pwm13_bus_clk.common.hw },
+	{ CLK_PWM14_BUS,	&pwm14_bus_clk.common.hw },
+	{ CLK_PWM15_BUS,	&pwm15_bus_clk.common.hw },
+	{ CLK_PWM16_BUS,	&pwm16_bus_clk.common.hw },
+	{ CLK_PWM17_BUS,	&pwm17_bus_clk.common.hw },
+	{ CLK_PWM18_BUS,	&pwm18_bus_clk.common.hw },
+	{ CLK_PWM19_BUS,	&pwm19_bus_clk.common.hw },
+	{ CLK_SSP3_BUS,		&ssp3_bus_clk.common.hw },
+	{ CLK_RTC_BUS,		&rtc_bus_clk.common.hw },
+	{ CLK_TWSI0_BUS,	&twsi0_bus_clk.common.hw },
+	{ CLK_TWSI1_BUS,	&twsi1_bus_clk.common.hw },
+	{ CLK_TWSI2_BUS,	&twsi2_bus_clk.common.hw },
+	{ CLK_TWSI4_BUS,	&twsi4_bus_clk.common.hw },
+	{ CLK_TWSI5_BUS,	&twsi5_bus_clk.common.hw },
+	{ CLK_TWSI6_BUS,	&twsi6_bus_clk.common.hw },
+	{ CLK_TWSI7_BUS,	&twsi7_bus_clk.common.hw },
+	{ CLK_TWSI8_BUS,	&twsi8_bus_clk.common.hw },
+	{ CLK_TIMERS1_BUS,	&timers1_bus_clk.common.hw },
+	{ CLK_TIMERS2_BUS,	&timers2_bus_clk.common.hw },
+	{ CLK_AIB_BUS,		&aib_bus_clk.common.hw },
+	{ CLK_ONEWIRE_BUS,	&onewire_bus_clk.common.hw },
+	{ CLK_SSPA0_BUS,	&sspa0_bus_clk.common.hw },
+	{ CLK_SSPA1_BUS,	&sspa1_bus_clk.common.hw },
+	{ CLK_TSEN_BUS,		&tsen_bus_clk.common.hw },
+	{ CLK_IPC_AP2AUD_BUS,	&ipc_ap2aud_bus_clk.common.hw },
+	{ 0,			NULL },
+};
+
+static struct spacemit_ccu_clk k1_ccu_apmu_clks[] = {
+	{ CLK_CCI550,		&cci550_clk.common.hw },
+	{ CLK_CPU_C0_HI,	&cpu_c0_hi_clk.common.hw },
+	{ CLK_CPU_C0_CORE,	&cpu_c0_core_clk.common.hw },
+	{ CLK_CPU_C0_ACE,	&cpu_c0_ace_clk.common.hw },
+	{ CLK_CPU_C0_TCM,	&cpu_c0_tcm_clk.common.hw },
+	{ CLK_CPU_C1_HI,	&cpu_c1_hi_clk.common.hw },
+	{ CLK_CPU_C1_CORE,	&cpu_c1_core_clk.common.hw },
+	{ CLK_CPU_C1_ACE,	&cpu_c1_ace_clk.common.hw },
+	{ CLK_CCIC_4X,		&ccic_4x_clk.common.hw },
+	{ CLK_CCIC1PHY,		&ccic1phy_clk.common.hw },
+	{ CLK_SDH_AXI,		&sdh_axi_aclk.common.hw },
+	{ CLK_SDH0,		&sdh0_clk.common.hw },
+	{ CLK_SDH1,		&sdh1_clk.common.hw },
+	{ CLK_SDH2,		&sdh2_clk.common.hw },
+	{ CLK_USB_P1,		&usb_p1_aclk.common.hw },
+	{ CLK_USB_AXI,		&usb_axi_clk.common.hw },
+	{ CLK_USB30,		&usb30_clk.common.hw },
+	{ CLK_QSPI,		&qspi_clk.common.hw },
+	{ CLK_QSPI_BUS,		&qspi_bus_clk.common.hw },
+	{ CLK_DMA,		&dma_clk.common.hw },
+	{ CLK_AES,		&aes_clk.common.hw },
+	{ CLK_VPU,		&vpu_clk.common.hw },
+	{ CLK_GPU,		&gpu_clk.common.hw },
+	{ CLK_EMMC,		&emmc_clk.common.hw },
+	{ CLK_EMMC_X,		&emmc_x_clk.common.hw },
+	{ CLK_AUDIO,		&audio_clk.common.hw },
+	{ CLK_HDMI,		&hdmi_mclk.common.hw },
+	{ CLK_PMUA_ACLK,	&pmua_aclk.common.hw },
+	{ CLK_PCIE0_MASTER,	&pcie0_master_clk.common.hw },
+	{ CLK_PCIE0_SLAVE,	&pcie0_slave_clk.common.hw },
+	{ CLK_PCIE0_DBI,	&pcie0_dbi_clk.common.hw },
+	{ CLK_PCIE1_MASTER,	&pcie1_master_clk.common.hw },
+	{ CLK_PCIE1_SLAVE,	&pcie1_slave_clk.common.hw },
+	{ CLK_PCIE1_DBI,	&pcie1_dbi_clk.common.hw },
+	{ CLK_PCIE2_MASTER,	&pcie2_master_clk.common.hw },
+	{ CLK_PCIE2_SLAVE,	&pcie2_slave_clk.common.hw },
+	{ CLK_PCIE2_DBI,	&pcie2_dbi_clk.common.hw },
+	{ CLK_EMAC0_BUS,	&emac0_bus_clk.common.hw },
+	{ CLK_EMAC0_PTP,	&emac0_ptp_clk.common.hw },
+	{ CLK_EMAC1_BUS,	&emac1_bus_clk.common.hw },
+	{ CLK_EMAC1_PTP,	&emac1_ptp_clk.common.hw },
+	{ CLK_JPG,		&jpg_clk.common.hw },
+	{ CLK_CCIC2PHY,		&ccic2phy_clk.common.hw },
+	{ CLK_CCIC3PHY,		&ccic3phy_clk.common.hw },
+	{ CLK_CSI,		&csi_clk.common.hw },
+	{ CLK_CAMM0,		&camm0_clk.common.hw },
+	{ CLK_CAMM1,		&camm1_clk.common.hw },
+	{ CLK_CAMM2,		&camm2_clk.common.hw },
+	{ CLK_ISP_CPP,		&isp_cpp_clk.common.hw },
+	{ CLK_ISP_BUS,		&isp_bus_clk.common.hw },
+	{ CLK_ISP,		&isp_clk.common.hw },
+	{ CLK_DPU_MCLK,		&dpu_mclk.common.hw },
+	{ CLK_DPU_ESC,		&dpu_esc_clk.common.hw },
+	{ CLK_DPU_BIT,		&dpu_bit_clk.common.hw },
+	{ CLK_DPU_PXCLK,	&dpu_pxclk.common.hw },
+	{ CLK_DPU_HCLK,		&dpu_hclk.common.hw },
+	{ CLK_DPU_SPI,		&dpu_spi_clk.common.hw },
+	{ CLK_DPU_SPI_HBUS,	&dpu_spi_hbus_clk.common.hw },
+	{ CLK_DPU_SPIBUS,	&dpu_spi_bus_clk.common.hw },
+	{ CLK_DPU_SPI_ACLK,	&dpu_spi_aclk.common.hw },
+	{ CLK_V2D,		&v2d_clk.common.hw },
+	{ CLK_EMMC_BUS,		&emmc_bus_clk.common.hw },
+	{ 0,			NULL },
+};
+
+static int spacemit_ccu_register(struct device *dev,
+				 struct regmap *regmap, struct regmap *lock_regmap,
+				 const struct spacemit_ccu_clk *clks)
+{
+	const struct spacemit_ccu_clk *clk;
+	int i, ret, max_id = 0;
+
+	for (clk = clks; clk->hw; clk++)
+		max_id = max(max_id, clk->id);
+
+	struct clk_hw_onecell_data *clk_data;
+
+	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, max_id + 1), GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+
+	for (i = 0; i <= max_id; i++)
+		clk_data->hws[i] = ERR_PTR(-ENOENT);
+
+	for (clk = clks; clk->hw; clk++) {
+		struct ccu_common *common = hw_to_ccu_common(clk->hw);
+		const char *name = clk->hw->init->name;
+
+		common->regmap		= regmap;
+		common->lock_regmap	= lock_regmap;
+
+		ret = devm_clk_hw_register(dev, clk->hw);
+		if (ret) {
+			dev_err(dev, "Cannot register clock %d - %s\n",
+				i, name);
+			return ret;
+		}
+
+		clk_data->hws[clk->id] = clk->hw;
+	}
+
+	clk_data->num = max_id + 1;
+
+	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+}
+
+static int k1_ccu_probe(struct platform_device *pdev)
+{
+	struct regmap *base_regmap, *lock_regmap = NULL;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	base_regmap = device_node_to_regmap(dev->of_node);
+	if (IS_ERR(base_regmap))
+		return dev_err_probe(dev, PTR_ERR(base_regmap),
+				     "failed to get regmap\n");
+
+	if (of_device_is_compatible(dev->of_node, "spacemit,k1-pll")) {
+		struct device_node *mpmu = of_parse_phandle(dev->of_node,
+							    "spacemit,mpmu", 0);
+		if (!mpmu)
+			return dev_err_probe(dev, -ENODEV,
+					     "Cannot parse MPMU region\n");
+
+		lock_regmap = device_node_to_regmap(mpmu);
+		of_node_put(mpmu);
+
+		if (IS_ERR(lock_regmap))
+			return dev_err_probe(dev, PTR_ERR(lock_regmap),
+					     "failed to get lock regmap\n");
+	}
+
+	ret = spacemit_ccu_register(dev, base_regmap, lock_regmap,
+				    of_device_get_match_data(dev));
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register clocks\n");
+
+	return 0;
+}
+
+static const struct of_device_id of_k1_ccu_match[] = {
+	{
+		.compatible	= "spacemit,k1-pll",
+		.data		= k1_ccu_apbs_clks,
+	},
+	{
+		.compatible	= "spacemit,k1-syscon-mpmu",
+		.data		= k1_ccu_mpmu_clks,
+	},
+	{
+		.compatible	= "spacemit,k1-syscon-apbc",
+		.data		= k1_ccu_apbc_clks,
+	},
+	{
+		.compatible	= "spacemit,k1-syscon-apmu",
+		.data		= k1_ccu_apmu_clks,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, of_k1_ccu_match);
+
+static struct platform_driver k1_ccu_driver = {
+	.driver = {
+		.name		= "spacemit,k1-ccu",
+		.of_match_table = of_k1_ccu_match,
+	},
+	.probe	= k1_ccu_probe,
+};
+module_platform_driver(k1_ccu_driver);
+
+MODULE_DESCRIPTION("Spacemit K1 CCU driver");
+MODULE_AUTHOR("Haylen Chu <heylenay@4d2.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
new file mode 100644
index 000000000000..494cde96fe3c
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_common.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#ifndef _CCU_COMMON_H_
+#define _CCU_COMMON_H_
+
+#include <linux/regmap.h>
+
+struct ccu_common {
+	struct regmap *regmap;
+	struct regmap *lock_regmap;
+
+	union {
+		/* For DDN and MIX */
+		struct {
+			u32 reg_ctrl;
+			u32 reg_fc;
+			u32 fc;
+		};
+
+		/* For PLL */
+		struct {
+			u32 reg_swcr1;
+			u32 reg_swcr2;
+			u32 reg_swcr3;
+		};
+	};
+
+	struct clk_hw hw;
+};
+
+static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
+{
+	return container_of(hw, struct ccu_common, hw);
+}
+
+#define ccu_read(reg, c, val)	regmap_read((c)->regmap, (c)->reg_##reg, val)
+#define ccu_update(reg, c, mask, val) \
+	regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)
+#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \
+	regmap_read_poll_timeout_atomic((c)->regmap, (c)->reg_##reg,	\
+					tmp, cond, sleep, timeout)
+
+#endif /* _CCU_COMMON_H_ */
diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c
new file mode 100644
index 000000000000..ee187687d0c4
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_ddn.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Spacemit clock type ddn
+ *
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/rational.h>
+
+#include "ccu_ddn.h"
+
+/*
+ * DDN stands for "Divider Denominator Numerator", it's M/N clock with a
+ * constant x2 factor. This clock hardware follows the equation below,
+ *
+ *	      numerator       Fin
+ *	2 * ------------- = -------
+ *	     denominator      Fout
+ *
+ * Thus, Fout could be calculated with,
+ *
+ *		Fin	denominator
+ *	Fout = ----- * -------------
+ *		 2	 numerator
+ */
+
+static unsigned long clk_ddn_calc_best_rate(struct ccu_ddn *ddn,
+					    unsigned long rate, unsigned long prate,
+					    unsigned long *num, unsigned long *den)
+{
+	rational_best_approximation(rate, prate / 2,
+				    ddn->den_mask, ddn->num_mask,
+				    den, num);
+	return prate / 2 * *den / *num;
+}
+
+static long clk_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long *prate)
+{
+	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
+	unsigned long num = 0, den = 0;
+
+	return clk_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
+}
+
+static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
+{
+	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
+	unsigned int val, num, den;
+
+	ccu_read(ctrl, &ddn->common, &val);
+
+	num = (val & ddn->num_mask) >> ddn->num_shift;
+	den = (val & ddn->den_mask) >> ddn->den_shift;
+
+	return prate / 2 * den / num;
+}
+
+static int clk_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long prate)
+{
+	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
+	unsigned long num, den;
+
+	clk_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
+
+	ccu_update(ctrl, &ddn->common,
+		   ddn->num_mask | ddn->den_mask,
+		   (num << ddn->num_shift) | (den << ddn->den_shift));
+
+	return 0;
+}
+
+const struct clk_ops spacemit_ccu_ddn_ops = {
+	.recalc_rate	= clk_ddn_recalc_rate,
+	.round_rate	= clk_ddn_round_rate,
+	.set_rate	= clk_ddn_set_rate,
+};
diff --git a/drivers/clk/spacemit/ccu_ddn.h b/drivers/clk/spacemit/ccu_ddn.h
new file mode 100644
index 000000000000..3746d084e1e7
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_ddn.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#ifndef _CCU_DDN_H_
+#define _CCU_DDN_H_
+
+#include <linux/clk-provider.h>
+
+#include "ccu_common.h"
+
+struct ccu_ddn {
+	struct ccu_common common;
+	unsigned int num_mask;
+	unsigned int num_shift;
+	unsigned int den_mask;
+	unsigned int den_shift;
+};
+
+#define CCU_DDN_INIT(_name, _parent, _flags) \
+	CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
+
+#define CCU_DDN_DEFINE(_name, _parent, _reg_ctrl,				\
+		       _num_mask, _num_shift, _den_mask, _den_shift,		\
+		       _flags)							\
+	struct ccu_ddn _name = {						\
+		.common = {							\
+			.reg_ctrl = _reg_ctrl,					\
+			.hw.init  = CCU_DDN_INIT(_name, _parent, _flags),	\
+		},								\
+		.num_mask = _num_mask,						\
+		.num_shift = _num_shift,					\
+		.den_mask = _den_mask,						\
+		.den_shift = _den_shift,					\
+	}
+
+static inline struct ccu_ddn *hw_to_ccu_ddn(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_ddn, common);
+}
+
+extern const struct clk_ops spacemit_ccu_ddn_ops;
+
+#endif
diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c
new file mode 100644
index 000000000000..a5c13000e062
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_mix.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Spacemit clock type mix(div/mux/gate/factor)
+ *
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#include <linux/clk-provider.h>
+
+#include "ccu_mix.h"
+
+#define MIX_TIMEOUT	10000
+
+static void ccu_gate_disable(struct clk_hw *hw)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+
+	ccu_update(ctrl, common, mix->gate.mask, 0);
+}
+
+static int ccu_gate_enable(struct clk_hw *hw)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	struct ccu_gate_config *gate = &mix->gate;
+
+	ccu_update(ctrl, common, gate->mask, gate->mask);
+
+	return 0;
+}
+
+static int ccu_gate_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	u32 tmp;
+
+	ccu_read(ctrl, common, &tmp);
+
+	return !!(tmp & mix->gate.mask);
+}
+
+static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+
+	return parent_rate * mix->factor.mul / mix->factor.div;
+}
+
+static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	struct ccu_div_config *div = &mix->div;
+	unsigned long val;
+	u32 reg;
+
+	ccu_read(ctrl, common, &reg);
+
+	val = reg >> div->shift;
+	val &= (1 << div->width) - 1;
+
+	val = divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
+
+	return val;
+}
+
+static int ccu_mix_trigger_fc(struct clk_hw *hw)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	unsigned int val = 0;
+
+	ccu_update(fc, common, common->fc, common->fc);
+
+	return ccu_poll(fc, common, val, !(val & common->fc),
+			5, MIX_TIMEOUT);
+}
+
+static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *prate)
+{
+	return ccu_factor_recalc_rate(hw, *prate);
+}
+
+static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	return 0;
+}
+
+static unsigned long
+ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
+		       struct clk_hw **best_parent,
+		       unsigned long *best_parent_rate,
+		       u32 *div_val)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	unsigned int parent_num = clk_hw_get_num_parents(hw);
+	struct ccu_div_config *div = &mix->div;
+	u32 div_max = 1 << div->width;
+	unsigned long best_rate = 0;
+
+	for (int i = 0; i < parent_num; i++) {
+		struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
+		unsigned long parent_rate;
+
+		if (!parent)
+			continue;
+
+		parent_rate = clk_hw_get_rate(parent);
+
+		for (int j = 1; j <= div_max; j++) {
+			unsigned long tmp = DIV_ROUND_UP_ULL(parent_rate, j);
+
+			if (abs(tmp - rate) < abs(best_rate - rate)) {
+				best_rate = tmp;
+
+				if (div_val)
+					*div_val = j - 1;
+
+				if (best_parent) {
+					*best_parent      = parent;
+					*best_parent_rate = parent_rate;
+				}
+			}
+		}
+	}
+
+	return best_rate;
+}
+
+static int ccu_mix_determine_rate(struct clk_hw *hw,
+				  struct clk_rate_request *req)
+{
+	req->rate = ccu_mix_calc_best_rate(hw, req->rate,
+					   &req->best_parent_hw,
+					   &req->best_parent_rate,
+					   NULL);
+	return 0;
+}
+
+static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	struct ccu_div_config *div = &mix->div;
+	int ret = 0, tmp = 0;
+	u32 current_div, target_div;
+
+	ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
+
+	ccu_read(ctrl, common, &tmp);
+
+	current_div = tmp >> div->shift;
+	current_div &= (1 << div->width) - 1;
+
+	if (current_div == target_div)
+		return 0;
+
+	tmp = GENMASK(div->width + div->shift - 1, div->shift);
+
+	ccu_update(ctrl, common, tmp, target_div << div->shift);
+
+	if (common->reg_fc)
+		ret = ccu_mix_trigger_fc(hw);
+
+	return ret;
+}
+
+static u8 ccu_mux_get_parent(struct clk_hw *hw)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	struct ccu_mux_config *mux = &mix->mux;
+	u32 reg;
+	u8 parent;
+
+	ccu_read(ctrl, common, &reg);
+
+	parent = reg >> mux->shift;
+	parent &= (1 << mux->width) - 1;
+
+	return parent;
+}
+
+static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct ccu_mix *mix = hw_to_ccu_mix(hw);
+	struct ccu_common *common = &mix->common;
+	struct ccu_mux_config *mux = &mix->mux;
+	int ret = 0;
+	u32 mask;
+
+	mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
+
+	ccu_update(ctrl, common, mask, index << mux->shift);
+
+	if (common->reg_fc)
+		ret = ccu_mix_trigger_fc(hw);
+
+	return ret;
+}
+
+const struct clk_ops spacemit_ccu_gate_ops = {
+	.disable	= ccu_gate_disable,
+	.enable		= ccu_gate_enable,
+	.is_enabled	= ccu_gate_is_enabled,
+};
+
+const struct clk_ops spacemit_ccu_factor_ops = {
+	.round_rate	= ccu_factor_round_rate,
+	.recalc_rate	= ccu_factor_recalc_rate,
+	.set_rate	= ccu_factor_set_rate,
+};
+
+const struct clk_ops spacemit_ccu_mux_ops = {
+	.determine_rate = ccu_mix_determine_rate,
+	.get_parent	= ccu_mux_get_parent,
+	.set_parent	= ccu_mux_set_parent,
+};
+
+const struct clk_ops spacemit_ccu_div_ops = {
+	.determine_rate = ccu_mix_determine_rate,
+	.recalc_rate	= ccu_div_recalc_rate,
+	.set_rate	= ccu_mix_set_rate,
+};
+
+const struct clk_ops spacemit_ccu_gate_factor_ops = {
+	.disable	= ccu_gate_disable,
+	.enable		= ccu_gate_enable,
+	.is_enabled	= ccu_gate_is_enabled,
+
+	.round_rate	= ccu_factor_round_rate,
+	.recalc_rate	= ccu_factor_recalc_rate,
+	.set_rate	= ccu_factor_set_rate,
+};
+
+const struct clk_ops spacemit_ccu_mux_gate_ops = {
+	.disable	= ccu_gate_disable,
+	.enable		= ccu_gate_enable,
+	.is_enabled	= ccu_gate_is_enabled,
+
+	.determine_rate = ccu_mix_determine_rate,
+	.get_parent	= ccu_mux_get_parent,
+	.set_parent	= ccu_mux_set_parent,
+};
+
+const struct clk_ops spacemit_ccu_div_gate_ops = {
+	.disable	= ccu_gate_disable,
+	.enable		= ccu_gate_enable,
+	.is_enabled	= ccu_gate_is_enabled,
+
+	.determine_rate = ccu_mix_determine_rate,
+	.recalc_rate	= ccu_div_recalc_rate,
+	.set_rate	= ccu_mix_set_rate,
+};
+
+const struct clk_ops spacemit_ccu_div_mux_gate_ops = {
+	.disable	= ccu_gate_disable,
+	.enable		= ccu_gate_enable,
+	.is_enabled	= ccu_gate_is_enabled,
+
+	.get_parent	= ccu_mux_get_parent,
+	.set_parent	= ccu_mux_set_parent,
+
+	.determine_rate = ccu_mix_determine_rate,
+	.recalc_rate	= ccu_div_recalc_rate,
+	.set_rate	= ccu_mix_set_rate,
+};
+
+const struct clk_ops spacemit_ccu_div_mux_ops = {
+	.get_parent	= ccu_mux_get_parent,
+	.set_parent	= ccu_mux_set_parent,
+
+	.determine_rate = ccu_mix_determine_rate,
+	.recalc_rate	= ccu_div_recalc_rate,
+	.set_rate	= ccu_mix_set_rate,
+};
diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h
new file mode 100644
index 000000000000..a3aa292d073d
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_mix.h
@@ -0,0 +1,246 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#ifndef _CCU_MIX_H_
+#define _CCU_MIX_H_
+
+#include <linux/clk-provider.h>
+
+#include "ccu_common.h"
+
+struct ccu_gate_config {
+	u32 mask;
+};
+
+struct ccu_factor_config {
+	u32 div;
+	u32 mul;
+};
+
+struct ccu_mux_config {
+	u8 shift;
+	u8 width;
+};
+
+struct ccu_div_config {
+	u8 shift;
+	u8 width;
+};
+
+struct ccu_mix {
+	struct ccu_factor_config factor;
+	struct ccu_gate_config gate;
+	struct ccu_div_config div;
+	struct ccu_mux_config mux;
+	struct ccu_common common;
+};
+
+#define CCU_GATE_INIT(_mask) { .mask = _mask }
+#define CCU_FACTOR_INIT(_div, _mul) { .div = _div, .mul = _mul }
+#define CCU_MUX_INIT(_shift, _width) { .shift = _shift, .width = _width }
+#define CCU_DIV_INIT(_shift, _width) { .shift = _shift, .width = _width }
+
+#define CCU_PARENT_HW(_parent)		{ .hw = &_parent.common.hw }
+#define CCU_PARENT_NAME(_name)		{ .fw_name = #_name }
+
+#define CCU_MIX_INITHW(_name, _parent, _ops, _flags)			\
+	(&(struct clk_init_data) {					\
+		.flags		= _flags,				\
+		.name		= #_name,				\
+		.parent_data	= (const struct clk_parent_data[])	\
+					{ _parent },			\
+		.num_parents	= 1,					\
+		.ops		= &_ops,				\
+	})
+
+#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags)		\
+	CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
+
+#define CCU_GATE_DEFINE(_name, _parent, _reg, _gate_mask, _flags)		\
+struct ccu_mix _name = {							\
+	.gate	= CCU_GATE_INIT(_gate_mask),					\
+	.common	= {								\
+		.reg_ctrl	= _reg,						\
+		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
+					  spacemit_ccu_gate_ops, _flags),	\
+	}									\
+}
+
+#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul)				\
+struct ccu_mix _name = {							\
+	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
+	.common = {								\
+		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
+					  spacemit_ccu_factor_ops, 0),		\
+	}									\
+}
+
+#define CCU_MUX_DEFINE(_name, _parents, _reg, _shift, _width, _flags)		\
+struct ccu_mix _name = {							\
+	.mux	= CCU_MUX_INIT(_shift, _width),					\
+	.common = {								\
+		.reg_ctrl	= _reg,						\
+		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
+						  spacemit_ccu_mux_ops,	_flags),\
+	}									\
+}
+
+#define CCU_DIV_DEFINE(_name, _parent, _reg, _shift, _width, _flags)		\
+struct ccu_mix _name = {							\
+	.div	= CCU_DIV_INIT(_shift, _width),					\
+	.common = {								\
+		.reg_ctrl	= _reg,						\
+		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
+					  spacemit_ccu_div_ops, _flags)		\
+	}									\
+}
+
+#define CCU_GATE_FACTOR_DEFINE(_name, _parent,					\
+			       _reg,						\
+			       _gate_mask,					\
+			       _div, _mul,					\
+			       _flags)						\
+struct ccu_mix _name = {							\
+	.gate	= CCU_GATE_INIT(_gate_mask),					\
+	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
+	.common = {								\
+		.reg_ctrl	= _reg,						\
+		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
+					  spacemit_ccu_gate_factor_ops, _flags)	\
+	}									\
+}
+
+#define CCU_MUX_GATE_DEFINE(_name, _parents,					\
+			    _reg,						\
+			    _shift, _width,					\
+			    _gate_mask,						\
+			    _flags)						\
+struct ccu_mix _name = {							\
+	.gate	= CCU_GATE_INIT(_gate_mask),					\
+	.mux	= CCU_MUX_INIT(_shift, _width),					\
+	.common = {								\
+		.reg_ctrl	= _reg,						\
+		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
+						  spacemit_ccu_mux_gate_ops,	\
+						  _flags),			\
+	}									\
+}
+
+#define CCU_DIV_GATE_DEFINE(_name, _parent,					\
+			    _reg,						\
+			    _shift, _width,					\
+			    _gate_mask,						\
+			    _flags)						\
+struct ccu_mix _name = {							\
+	.gate	= CCU_GATE_INIT(_gate_mask),					\
+	.div	= CCU_DIV_INIT(_shift, _width),					\
+	.common = {								\
+		.reg_ctrl	= _reg,						\
+		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
+					  spacemit_ccu_div_gate_ops, _flags),	\
+	}									\
+}
+
+#define CCU_DIV_MUX_GATE_DEFINE(_name, _parents,				\
+				_reg_ctrl,					\
+				_mshift, _mwidth, _muxshift, _muxwidth,		\
+				_gate_mask,					\
+				_flags)						\
+struct ccu_mix _name = {							\
+	.gate	= CCU_GATE_INIT(_gate_mask),					\
+	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
+	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
+	.common	= {								\
+		.reg_ctrl	= _reg_ctrl,					\
+		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
+						  spacemit_ccu_div_mux_gate_ops,\
+						  _flags),			\
+	},									\
+}
+
+#define CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents,			\
+					 _reg_ctrl, _reg_fc,			\
+					 _mshift, _mwidth,			\
+					 _fc,					\
+					 _muxshift, _muxwidth,			\
+					 _gate_mask,				\
+					 _flags)				\
+struct ccu_mix _name = {							\
+	.gate	= CCU_GATE_INIT(_gate_mask),					\
+	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
+	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
+	.common = {								\
+		.reg_ctrl	= _reg_ctrl,					\
+		.reg_fc		= _reg_fc,					\
+		.fc		= _fc,						\
+		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
+						  spacemit_ccu_div_mux_gate_ops,\
+						  _flags),			\
+	},									\
+}
+
+#define CCU_DIV_FC_MUX_GATE_DEFINE(_name, _parents,				\
+				   _reg_ctrl,					\
+				   _mshift, _mwidth,				\
+				   _fc,						\
+				   _muxshift, _muxwidth,			\
+				   _gate_mask, _flags)		\
+CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents, _reg_ctrl, _reg_ctrl,		\
+				 _mshift, _mwidth, _fc, _muxshift, _muxwidth,		\
+				 _gate_mask, _flags)
+
+#define CCU_DIV_FC_MUX_DEFINE(_name, _parents,					\
+			      _reg_ctrl,					\
+			      _mshift, _mwidth,					\
+			      _fc,						\
+			      _muxshift, _muxwidth,				\
+			      _flags)						\
+struct ccu_mix _name = {							\
+	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
+	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
+	.common = {								\
+		.reg_ctrl	= _reg_ctrl,					\
+		.reg_fc		= _reg_ctrl,					\
+		.fc		= _fc,						\
+		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
+						  spacemit_ccu_div_mux_ops,	\
+						  _flags),			\
+	},									\
+}
+
+#define CCU_MUX_FC_DEFINE(_name, _parents,					\
+			  _reg_ctrl,						\
+			  _fc,							\
+			  _muxshift, _muxwidth,					\
+			  _flags)						\
+struct ccu_mix _name = {							\
+	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
+	.common = {								\
+		.reg_ctrl	= _reg_ctrl,					\
+		.reg_fc		= _reg_ctrl,					\
+		.fc		= _fc,						\
+		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
+						  spacemit_ccu_mux_ops,	_flags)	\
+	},									\
+}
+
+static inline struct ccu_mix *hw_to_ccu_mix(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_mix, common);
+}
+
+extern const struct clk_ops spacemit_ccu_gate_ops, spacemit_ccu_factor_ops;
+extern const struct clk_ops spacemit_ccu_mux_ops, spacemit_ccu_div_ops;
+
+extern const struct clk_ops spacemit_ccu_gate_factor_ops;
+extern const struct clk_ops spacemit_ccu_div_gate_ops;
+extern const struct clk_ops spacemit_ccu_mux_gate_ops;
+extern const struct clk_ops spacemit_ccu_div_mux_ops;
+
+extern const struct clk_ops spacemit_ccu_div_mux_gate_ops;
+#endif /* _CCU_DIV_H_ */
diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
new file mode 100644
index 000000000000..9df2149f6c98
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_pll.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Spacemit clock type pll
+ *
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include "ccu_common.h"
+#include "ccu_pll.h"
+
+#define PLL_DELAY_TIME	3000
+
+#define PLL_SWCR3_EN	BIT(31)
+
+static int ccu_pll_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_pll *p = hw_to_ccu_pll(hw);
+	u32 tmp;
+
+	ccu_read(swcr3, &p->common, &tmp);
+
+	return tmp & PLL_SWCR3_EN;
+}
+
+/* frequency unit Mhz, return pll vco freq */
+static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
+{
+	const struct ccu_pll_rate_tbl *pll_rate_table;
+	struct ccu_pll *p = hw_to_ccu_pll(hw);
+	struct ccu_common *common = &p->common;
+	u32 swcr1, swcr3, size;
+	int i;
+
+	ccu_read(swcr1, common, &swcr1);
+	ccu_read(swcr3, common, &swcr3);
+	swcr3 &= ~PLL_SWCR3_EN;
+
+	pll_rate_table = p->pll.rate_tbl;
+	size = p->pll.tbl_size;
+
+	for (i = 0; i < size; i++) {
+		if (pll_rate_table[i].swcr1 == swcr1 &&
+		    pll_rate_table[i].swcr3 == swcr3)
+			return pll_rate_table[i].rate;
+	}
+
+	WARN_ON_ONCE(1);
+
+	return 0;
+}
+
+static int ccu_pll_enable(struct clk_hw *hw)
+{
+	struct ccu_pll *p = hw_to_ccu_pll(hw);
+	struct ccu_common *common = &p->common;
+	unsigned int tmp;
+	int ret;
+
+	if (ccu_pll_is_enabled(hw))
+		return 0;
+
+	ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN);
+
+	/* check lock status */
+	ret = regmap_read_poll_timeout_atomic(common->lock_regmap,
+					      p->pll.reg_lock,
+					      tmp,
+					      tmp & p->pll.lock_enable_bit,
+					      5, PLL_DELAY_TIME);
+
+	return ret;
+}
+
+static void ccu_pll_disable(struct clk_hw *hw)
+{
+	struct ccu_pll *p = hw_to_ccu_pll(hw);
+	struct ccu_common *common = &p->common;
+
+	ccu_update(swcr3, common, PLL_SWCR3_EN, 0);
+}
+
+/*
+ * PLLs must be gated before changing rate, which is ensured by
+ * flag CLK_SET_RATE_GATE.
+ */
+static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct ccu_pll *p = hw_to_ccu_pll(hw);
+	struct ccu_common *common = &p->common;
+	struct ccu_pll_config *params = &p->pll;
+	const struct ccu_pll_rate_tbl *entry = NULL;
+	int i;
+
+	for (i = 0; i < params->tbl_size; i++) {
+		if (rate == params->rate_tbl[i].rate) {
+			entry = &params->rate_tbl[i];
+			break;
+		}
+	}
+
+	if (WARN_ON_ONCE(!entry))
+		return -EINVAL;
+
+	ccu_update(swcr1, common, entry->swcr1, entry->swcr1);
+	ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3);
+
+	return 0;
+}
+
+static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	return ccu_pll_get_vco_freq(hw);
+}
+
+static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long *prate)
+{
+	struct ccu_pll *p = hw_to_ccu_pll(hw);
+	struct ccu_pll_config *params = &p->pll;
+	unsigned int i;
+
+	for (i = 0; i < params->tbl_size; i++) {
+		if (params->rate_tbl[i].rate > rate) {
+			i--;
+			break;
+		}
+	}
+
+	return rate;
+}
+
+const struct clk_ops spacemit_ccu_pll_ops = {
+	.enable		= ccu_pll_enable,
+	.disable	= ccu_pll_disable,
+	.set_rate	= ccu_pll_set_rate,
+	.recalc_rate	= ccu_pll_recalc_rate,
+	.round_rate	= ccu_pll_round_rate,
+	.is_enabled	= ccu_pll_is_enabled,
+};
+
diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h
new file mode 100644
index 000000000000..c6a3a5cce995
--- /dev/null
+++ b/drivers/clk/spacemit/ccu_pll.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2024 SpacemiT Technology Co. Ltd
+ * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
+ */
+
+#ifndef _CCU_PLL_H_
+#define _CCU_PLL_H_
+
+#include <linux/clk-provider.h>
+
+#include "ccu_common.h"
+
+struct ccu_pll_rate_tbl {
+	unsigned long rate;
+	u32 swcr1;
+	u32 swcr3;
+};
+
+struct ccu_pll_config {
+	const struct ccu_pll_rate_tbl *rate_tbl;
+	u32 tbl_size;
+	u32 reg_lock;
+	u32 lock_enable_bit;
+};
+
+#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
+	{									\
+		.rate	= _rate,							\
+		.swcr1	= _swcr1,						\
+		.swcr3	= _swcr3,						\
+	}
+
+struct ccu_pll {
+	struct ccu_pll_config	pll;
+	struct ccu_common	common;
+};
+
+#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \
+	{									\
+		.rate_tbl	 = _table,					\
+		.tbl_size	 = ARRAY_SIZE(_table),				\
+		.reg_lock	 = (_reg_lock),					\
+		.lock_enable_bit = (_lock_enable_bit),				\
+	}
+
+#define CCU_PLL_HWINIT(_name, _flags)						\
+	(&(struct clk_init_data) {						\
+		.name		= #_name,					\
+		.ops		= &spacemit_ccu_pll_ops,			\
+		.parent_data	= &(struct clk_parent_data) { .index = 0 },	\
+		.num_parents	= 1,						\
+		.flags		= _flags,					\
+	})
+
+#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3,			\
+		       _reg_lock, _lock_enable_bit, _flags)			\
+	struct ccu_pll _name = {						\
+		.pll	= CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit),	\
+		.common = {							\
+			.reg_swcr1	= _reg_swcr1,				\
+			.reg_swcr3	= _reg_swcr3,				\
+			.hw.init	= CCU_PLL_HWINIT(_name, _flags)		\
+		}								\
+	}
+
+static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_pll, common);
+}
+
+extern const struct clk_ops spacemit_ccu_pll_ops;
+
+#endif
-- 
2.48.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* [PATCH v5 4/5] clk: spacemit: k1: Add TWSI8 bus and function clocks
  2025-03-06 17:57 [PATCH v5 0/5] Add clock controller support for SpacemiT K1 Haylen Chu
                   ` (2 preceding siblings ...)
  2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
@ 2025-03-06 17:57 ` Haylen Chu
  2025-03-07  6:30   ` Haylen Chu
  2025-03-06 17:57 ` [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1 Haylen Chu
  4 siblings, 1 reply; 26+ messages in thread
From: Haylen Chu @ 2025-03-06 17:57 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu

The control register for TWSI8 clocks, APBC_TWSI8_CLK_RST, contains mux
selection bits, reset assertion bit and enable bits for function and bus
clocks. It has a quirk that reading always results in zero.

As a workaround, let's hardcode the mux value as zero to select
pll1_d78_31p5 as parent and treat twsi8_clk as a gate, whose enable mask
is combined from the real bus and function clocks to avoid the
write-only register being shared between two clk_hws, in which case
updates of one clk_hw zero the other's bits.

With a 1:1 factor serving as placeholder for the bus clock, the I2C-8
controller could be brought up, which is essential for boards attaching
power-management chips to it.

Signed-off-by: Haylen Chu <heylenay@4d2.org>
---
 drivers/clk/spacemit/ccu-k1.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
index 5974a0a1b5f6..44db48ae7131 100644
--- a/drivers/clk/spacemit/ccu-k1.c
+++ b/drivers/clk/spacemit/ccu-k1.c
@@ -558,6 +558,10 @@ static CCU_MUX_GATE_DEFINE(twsi7_clk, twsi_parents,
 			   APBC_TWSI7_CLK_RST,
 			   4, 3, BIT(1),
 			   0);
+static CCU_GATE_DEFINE(twsi8_clk, CCU_PARENT_HW(pll1_d78_31p5),
+		       APBC_TWSI8_CLK_RST,
+		       BIT(1) | BIT(0),
+		       0);
 
 static const struct clk_parent_data timer_parents[] = {
 	CCU_PARENT_HW(pll1_d192_12p8),
@@ -795,6 +799,8 @@ static CCU_GATE_DEFINE(twsi7_bus_clk, CCU_PARENT_HW(apb_clk),
 		       APBC_TWSI7_CLK_RST,
 		       BIT(0),
 		       0);
+static CCU_FACTOR_DEFINE(twsi8_bus_clk, CCU_PARENT_HW(apb_clk),
+			 1, 1);
 
 static CCU_GATE_DEFINE(timers1_bus_clk, CCU_PARENT_HW(apb_clk),
 		       APBC_TIMERS1_CLK_RST,
-- 
2.48.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1
  2025-03-06 17:57 [PATCH v5 0/5] Add clock controller support for SpacemiT K1 Haylen Chu
                   ` (3 preceding siblings ...)
  2025-03-06 17:57 ` [PATCH v5 4/5] clk: spacemit: k1: Add TWSI8 bus and function clocks Haylen Chu
@ 2025-03-06 17:57 ` Haylen Chu
  2025-03-07  1:55   ` Inochi Amaoto
  4 siblings, 1 reply; 26+ messages in thread
From: Haylen Chu @ 2025-03-06 17:57 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu

Describe the PLL and system controllers that're capable of generating
clock signals in the devicetree.

Signed-off-by: Haylen Chu <heylenay@4d2.org>
---
 arch/riscv/boot/dts/spacemit/k1.dtsi | 79 ++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi
index c670ebf8fa12..09a9100986b1 100644
--- a/arch/riscv/boot/dts/spacemit/k1.dtsi
+++ b/arch/riscv/boot/dts/spacemit/k1.dtsi
@@ -3,6 +3,8 @@
  * Copyright (C) 2024 Yangyu Chen <cyy@cyyself.name>
  */
 
+#include <dt-bindings/clock/spacemit,k1-ccu.h>
+
 /dts-v1/;
 / {
 	#address-cells = <2>;
@@ -306,6 +308,40 @@ cluster1_l2_cache: l2-cache1 {
 		};
 	};
 
+	clocks {
+		#address-cells = <0x2>;
+		#size-cells = <0x2>;
+		ranges;
+
+		vctcxo_1m: clock-1m {
+			compatible = "fixed-clock";
+			clock-frequency = <1000000>;
+			clock-output-names = "vctcxo_1m";
+			#clock-cells = <0>;
+		};
+
+		vctcxo_24m: clock-24m {
+			compatible = "fixed-clock";
+			clock-frequency = <24000000>;
+			clock-output-names = "vctcxo_24m";
+			#clock-cells = <0>;
+		};
+
+		vctcxo_3m: clock-3m {
+			compatible = "fixed-clock";
+			clock-frequency = <3000000>;
+			clock-output-names = "vctcxo_3m";
+			#clock-cells = <0>;
+		};
+
+		osc_32k: clock-32k {
+			compatible = "fixed-clock";
+			clock-frequency = <32000>;
+			clock-output-names = "osc_32k";
+			#clock-cells = <0>;
+		};
+	};
+
 	soc {
 		compatible = "simple-bus";
 		interrupt-parent = <&plic>;
@@ -314,6 +350,17 @@ soc {
 		dma-noncoherent;
 		ranges;
 
+		syscon_apbc: system-control@d4015000 {
+			compatible = "spacemit,k1-syscon-apbc";
+			reg = <0x0 0xd4015000 0x0 0x1000>;
+			clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>,
+				 <&vctcxo_24m>;
+			clock-names = "osc", "vctcxo_1m", "vctcxo_3m",
+				      "vctcxo_24m";
+			#clock-cells = <1>;
+			#reset-cells = <1>;
+		};
+
 		uart0: serial@d4017000 {
 			compatible = "spacemit,k1-uart", "intel,xscale-uart";
 			reg = <0x0 0xd4017000 0x0 0x100>;
@@ -409,6 +456,38 @@ pinctrl: pinctrl@d401e000 {
 			reg = <0x0 0xd401e000 0x0 0x400>;
 		};
 
+		syscon_mpmu: system-controller@d4050000 {
+			compatible = "spacemit,k1-syscon-mpmu";
+			reg = <0x0 0xd4050000 0x0 0x209c>;
+			clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>,
+				 <&vctcxo_24m>;
+			clock-names = "osc", "vctcxo_1m", "vctcxo_3m",
+				      "vctcxo_24m";
+			#clock-cells = <1>;
+			#power-domain-cells = <1>;
+			#reset-cells = <1>;
+		};
+
+		pll: system-control@d4090000 {
+			compatible = "spacemit,k1-pll";
+			reg = <0x0 0xd4090000 0x0 0x1000>;
+			clocks = <&vctcxo_24m>;
+			spacemit,mpmu = <&syscon_mpmu>;
+			#clock-cells = <1>;
+		};
+
+		syscon_apmu: system-control@d4282800 {
+			compatible = "spacemit,k1-syscon-apmu";
+			reg = <0x0 0xd4282800 0x0 0x400>;
+			clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>,
+				 <&vctcxo_24m>;
+			clock-names = "osc", "vctcxo_1m", "vctcxo_3m",
+				      "vctcxo_24m";
+			#clock-cells = <1>;
+			#power-domain-cells = <1>;
+			#reset-cells = <1>;
+		};
+
 		plic: interrupt-controller@e0000000 {
 			compatible = "spacemit,k1-plic", "sifive,plic-1.0.0";
 			reg = <0x0 0xe0000000 0x0 0x4000000>;
-- 
2.48.1


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll
  2025-03-06 17:57 ` [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll Haylen Chu
@ 2025-03-07  0:29   ` Yixun Lan
  2025-03-07  6:34     ` Haylen Chu
  2025-03-07  8:19   ` Krzysztof Kozlowski
  1 sibling, 1 reply; 26+ messages in thread
From: Yixun Lan @ 2025-03-07  0:29 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

Hi Haylen:

On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> Add definition for the PLL found on Spacemit K1 SoC, which takes the
> external 24MHz oscillator as input and generates clocks in various
> frequencies for the system.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> ---
>  .../bindings/clock/spacemit,k1-pll.yaml       | 50 +++++++++++++++++++
>  include/dt-bindings/clock/spacemit,k1-ccu.h   | 37 ++++++++++++++
>  2 files changed, 87 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
> 
> diff --git a/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml b/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
> new file mode 100644
> index 000000000000..23d7aa1bc573
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
> @@ -0,0 +1,50 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/spacemit,k1-pll.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Spacemit K1 PLL
> +
> +maintainers:
> +  - Haylen Chu <heylenay@4d2.org>
> +
> +properties:
> +  compatible:
> +    const: spacemit,k1-pll
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    description: External 24MHz oscillator
> +
> +  spacemit,mpmu:
how about naming it as "spacemit,mpmu-syscon" explicitly?
to indicate this is a syscon phandle, it's more readable

> +    $ref: /schemas/types.yaml#/definitions/phandle
> +    description:
> +      Phandle to the "Main PMU (MPMU)" syscon. It is used to check PLL
> +      lock status.
> +
> +  "#clock-cells":
> +    const: 1
> +    description:
> +      See <dt-bindings/clock/spacemit,k1-ccu.h> for valid indices.
> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - spacemit,mpmu
> +  - "#clock-cells"
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    clock-controller@d4090000 {
> +        compatible = "spacemit,k1-pll";
> +        reg = <0xd4090000 0x1000>;
> +        clocks = <&vctcxo_24m>;
> +        spacemit,mpmu = <&sysctl_mpmu>;
> +        #clock-cells = <1>;
> +    };
> diff --git a/include/dt-bindings/clock/spacemit,k1-ccu.h b/include/dt-bindings/clock/spacemit,k1-ccu.h
> index 1f8b01db44ca..4a0c7163257e 100644
> --- a/include/dt-bindings/clock/spacemit,k1-ccu.h
> +++ b/include/dt-bindings/clock/spacemit,k1-ccu.h
> @@ -6,6 +6,43 @@
>  #ifndef _DT_BINDINGS_SPACEMIT_CCU_H_
>  #define _DT_BINDINGS_SPACEMIT_CCU_H_
>  
> +/*	APBS (PLL) clocks	*/
> +#define CLK_PLL1		0
> +#define CLK_PLL2		1
> +#define CLK_PLL3		2
> +#define CLK_PLL1_D2		3
> +#define CLK_PLL1_D3		4
> +#define CLK_PLL1_D4		5
> +#define CLK_PLL1_D5		6
> +#define CLK_PLL1_D6		7
> +#define CLK_PLL1_D7		8
> +#define CLK_PLL1_D8		9
> +#define CLK_PLL1_D11		10
> +#define CLK_PLL1_D13		11
> +#define CLK_PLL1_D23		12
> +#define CLK_PLL1_D64		13
> +#define CLK_PLL1_D10_AUD	14
> +#define CLK_PLL1_D100_AUD	15
> +#define CLK_PLL2_D1		16
> +#define CLK_PLL2_D2		17
> +#define CLK_PLL2_D3		18
> +#define CLK_PLL2_D4		19
> +#define CLK_PLL2_D5		20
> +#define CLK_PLL2_D6		21
> +#define CLK_PLL2_D7		22
> +#define CLK_PLL2_D8		23
> +#define CLK_PLL3_D1		24
> +#define CLK_PLL3_D2		25
> +#define CLK_PLL3_D3		26
> +#define CLK_PLL3_D4		27
> +#define CLK_PLL3_D5		28
> +#define CLK_PLL3_D6		29
> +#define CLK_PLL3_D7		30
> +#define CLK_PLL3_D8		31
> +#define CLK_PLL3_80		32
> +#define CLK_PLL3_40		33
> +#define CLK_PLL3_20		34
> +
>  /*	MPMU clocks	*/
>  #define CLK_PLL1_307P2		0
>  #define CLK_PLL1_76P8		1
> -- 
> 2.48.1
> 

-- 
Yixun Lan (dlan)
Gentoo Linux Developer
GPG Key ID AABEFD55

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
@ 2025-03-07  0:51   ` Yixun Lan
  2025-03-07  6:42     ` Haylen Chu
  2025-03-11 23:19   ` Alex Elder
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 26+ messages in thread
From: Yixun Lan @ 2025-03-07  0:51 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> The clock tree of K1 SoC contains three main types of clock hardware
> (PLL/DDN/MIX) and has control registers split into several multifunction
> devices: APBS (PLLs), MPMU, APBC and APMU.
> 
> All register operations are done through regmap to ensure atomiciy
> between concurrent operations of clock driver and reset,
> power-domain driver that will be introduced in the future.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> ---
>  drivers/clk/Kconfig               |    1 +
>  drivers/clk/Makefile              |    1 +
>  drivers/clk/spacemit/Kconfig      |   20 +
>  drivers/clk/spacemit/Makefile     |    5 +
>  drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
>  drivers/clk/spacemit/ccu_common.h |   47 +
>  drivers/clk/spacemit/ccu_ddn.c    |   80 ++
>  drivers/clk/spacemit/ccu_ddn.h    |   48 +
>  drivers/clk/spacemit/ccu_mix.c    |  284 +++++
>  drivers/clk/spacemit/ccu_mix.h    |  246 +++++
>  drivers/clk/spacemit/ccu_pll.c    |  146 +++
>  drivers/clk/spacemit/ccu_pll.h    |   76 ++
>  12 files changed, 2668 insertions(+)
>  create mode 100644 drivers/clk/spacemit/Kconfig
>  create mode 100644 drivers/clk/spacemit/Makefile
>  create mode 100644 drivers/clk/spacemit/ccu-k1.c
>  create mode 100644 drivers/clk/spacemit/ccu_common.h
>  create mode 100644 drivers/clk/spacemit/ccu_ddn.c
>  create mode 100644 drivers/clk/spacemit/ccu_ddn.h
>  create mode 100644 drivers/clk/spacemit/ccu_mix.c
>  create mode 100644 drivers/clk/spacemit/ccu_mix.h
>  create mode 100644 drivers/clk/spacemit/ccu_pll.c
>  create mode 100644 drivers/clk/spacemit/ccu_pll.h
> 
..
> +static int k1_ccu_probe(struct platform_device *pdev)
> +{
> +	struct regmap *base_regmap, *lock_regmap = NULL;
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	base_regmap = device_node_to_regmap(dev->of_node);
> +	if (IS_ERR(base_regmap))
> +		return dev_err_probe(dev, PTR_ERR(base_regmap),
> +				     "failed to get regmap\n");
> +
> +	if (of_device_is_compatible(dev->of_node, "spacemit,k1-pll")) {
..
> +		struct device_node *mpmu = of_parse_phandle(dev->of_node,
> +							    "spacemit,mpmu", 0);
> +		if (!mpmu)
> +			return dev_err_probe(dev, -ENODEV,
> +					     "Cannot parse MPMU region\n");
> +
> +		lock_regmap = device_node_to_regmap(mpmu);
> +		of_node_put(mpmu);
> +
you can simplify above with syscon_regmap_lookup_by_phandle(), which
would save a few lines

or further, just call syscon_regmap_lookup_by_compatible()? then
won't be necessary to introduce the "spacemit,mpmu" property..

> +		if (IS_ERR(lock_regmap))
> +			return dev_err_probe(dev, PTR_ERR(lock_regmap),
> +					     "failed to get lock regmap\n");
> +	}
> +
> +	ret = spacemit_ccu_register(dev, base_regmap, lock_regmap,
> +				    of_device_get_match_data(dev));
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to register clocks\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id of_k1_ccu_match[] = {
> +	{
> +		.compatible	= "spacemit,k1-pll",
> +		.data		= k1_ccu_apbs_clks,
> +	},
> +	{
> +		.compatible	= "spacemit,k1-syscon-mpmu",
> +		.data		= k1_ccu_mpmu_clks,
> +	},
> +	{
> +		.compatible	= "spacemit,k1-syscon-apbc",
> +		.data		= k1_ccu_apbc_clks,
> +	},
> +	{
> +		.compatible	= "spacemit,k1-syscon-apmu",
> +		.data		= k1_ccu_apmu_clks,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, of_k1_ccu_match);
> +
> +static struct platform_driver k1_ccu_driver = {
> +	.driver = {
> +		.name		= "spacemit,k1-ccu",
> +		.of_match_table = of_k1_ccu_match,
> +	},
> +	.probe	= k1_ccu_probe,
> +};
> +module_platform_driver(k1_ccu_driver);
> +
> +MODULE_DESCRIPTION("Spacemit K1 CCU driver");
> +MODULE_AUTHOR("Haylen Chu <heylenay@4d2.org>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
> new file mode 100644
> index 000000000000..494cde96fe3c
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_common.h
> @@ -0,0 +1,47 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_COMMON_H_
> +#define _CCU_COMMON_H_
> +
> +#include <linux/regmap.h>
> +
> +struct ccu_common {
> +	struct regmap *regmap;
> +	struct regmap *lock_regmap;
> +
> +	union {
> +		/* For DDN and MIX */
> +		struct {
> +			u32 reg_ctrl;
> +			u32 reg_fc;
> +			u32 fc;
> +		};
> +
> +		/* For PLL */
> +		struct {
> +			u32 reg_swcr1;
> +			u32 reg_swcr2;
> +			u32 reg_swcr3;
> +		};
> +	};
> +
> +	struct clk_hw hw;
> +};
> +
> +static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
> +{
> +	return container_of(hw, struct ccu_common, hw);
> +}
> +
> +#define ccu_read(reg, c, val)	regmap_read((c)->regmap, (c)->reg_##reg, val)
> +#define ccu_update(reg, c, mask, val) \
> +	regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)
> +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \
> +	regmap_read_poll_timeout_atomic((c)->regmap, (c)->reg_##reg,	\
> +					tmp, cond, sleep, timeout)
> +
> +#endif /* _CCU_COMMON_H_ */
> diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c
> new file mode 100644
> index 000000000000..ee187687d0c4
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_ddn.c
> @@ -0,0 +1,80 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit clock type ddn
> + *
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/rational.h>
> +
> +#include "ccu_ddn.h"
> +
> +/*
> + * DDN stands for "Divider Denominator Numerator", it's M/N clock with a
> + * constant x2 factor. This clock hardware follows the equation below,
> + *
> + *	      numerator       Fin
> + *	2 * ------------- = -------
> + *	     denominator      Fout
> + *
> + * Thus, Fout could be calculated with,
> + *
> + *		Fin	denominator
> + *	Fout = ----- * -------------
> + *		 2	 numerator
> + */
> +
> +static unsigned long clk_ddn_calc_best_rate(struct ccu_ddn *ddn,
> +					    unsigned long rate, unsigned long prate,
> +					    unsigned long *num, unsigned long *den)
> +{
> +	rational_best_approximation(rate, prate / 2,
> +				    ddn->den_mask, ddn->num_mask,
> +				    den, num);
> +	return prate / 2 * *den / *num;
> +}
> +
> +static long clk_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long *prate)
> +{
> +	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
> +	unsigned long num = 0, den = 0;
> +
> +	return clk_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
> +}
> +
> +static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
> +{
> +	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
> +	unsigned int val, num, den;
> +
> +	ccu_read(ctrl, &ddn->common, &val);
> +
> +	num = (val & ddn->num_mask) >> ddn->num_shift;
> +	den = (val & ddn->den_mask) >> ddn->den_shift;
> +
> +	return prate / 2 * den / num;
> +}
> +
> +static int clk_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long prate)
> +{
> +	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
> +	unsigned long num, den;
> +
> +	clk_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
> +
> +	ccu_update(ctrl, &ddn->common,
> +		   ddn->num_mask | ddn->den_mask,
> +		   (num << ddn->num_shift) | (den << ddn->den_shift));
> +
> +	return 0;
> +}
> +
> +const struct clk_ops spacemit_ccu_ddn_ops = {
> +	.recalc_rate	= clk_ddn_recalc_rate,
> +	.round_rate	= clk_ddn_round_rate,
> +	.set_rate	= clk_ddn_set_rate,
> +};
> diff --git a/drivers/clk/spacemit/ccu_ddn.h b/drivers/clk/spacemit/ccu_ddn.h
> new file mode 100644
> index 000000000000..3746d084e1e7
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_ddn.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_DDN_H_
> +#define _CCU_DDN_H_
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_common.h"
> +
> +struct ccu_ddn {
> +	struct ccu_common common;
> +	unsigned int num_mask;
> +	unsigned int num_shift;
> +	unsigned int den_mask;
> +	unsigned int den_shift;
> +};
> +
> +#define CCU_DDN_INIT(_name, _parent, _flags) \
> +	CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
> +
> +#define CCU_DDN_DEFINE(_name, _parent, _reg_ctrl,				\
> +		       _num_mask, _num_shift, _den_mask, _den_shift,		\
> +		       _flags)							\
> +	struct ccu_ddn _name = {						\
> +		.common = {							\
> +			.reg_ctrl = _reg_ctrl,					\
> +			.hw.init  = CCU_DDN_INIT(_name, _parent, _flags),	\
> +		},								\
> +		.num_mask = _num_mask,						\
> +		.num_shift = _num_shift,					\
> +		.den_mask = _den_mask,						\
> +		.den_shift = _den_shift,					\
> +	}
> +
> +static inline struct ccu_ddn *hw_to_ccu_ddn(struct clk_hw *hw)
> +{
> +	struct ccu_common *common = hw_to_ccu_common(hw);
> +
> +	return container_of(common, struct ccu_ddn, common);
> +}
> +
> +extern const struct clk_ops spacemit_ccu_ddn_ops;
> +
> +#endif
> diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c
> new file mode 100644
> index 000000000000..a5c13000e062
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_mix.c
> @@ -0,0 +1,284 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit clock type mix(div/mux/gate/factor)
> + *
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_mix.h"
> +
> +#define MIX_TIMEOUT	10000
> +
> +static void ccu_gate_disable(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +
> +	ccu_update(ctrl, common, mix->gate.mask, 0);
> +}
> +
> +static int ccu_gate_enable(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_gate_config *gate = &mix->gate;
> +
> +	ccu_update(ctrl, common, gate->mask, gate->mask);
> +
> +	return 0;
> +}
> +
> +static int ccu_gate_is_enabled(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	u32 tmp;
> +
> +	ccu_read(ctrl, common, &tmp);
> +
> +	return !!(tmp & mix->gate.mask);
> +}
> +
> +static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +
> +	return parent_rate * mix->factor.mul / mix->factor.div;
> +}
> +
> +static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_div_config *div = &mix->div;
> +	unsigned long val;
> +	u32 reg;
> +
> +	ccu_read(ctrl, common, &reg);
> +
> +	val = reg >> div->shift;
> +	val &= (1 << div->width) - 1;
> +
> +	val = divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
> +
> +	return val;
> +}
> +
> +static int ccu_mix_trigger_fc(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	unsigned int val = 0;
> +
> +	ccu_update(fc, common, common->fc, common->fc);
> +
> +	return ccu_poll(fc, common, val, !(val & common->fc),
> +			5, MIX_TIMEOUT);
> +}
> +
> +static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *prate)
> +{
> +	return ccu_factor_recalc_rate(hw, *prate);
> +}
> +
> +static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static unsigned long
> +ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
> +		       struct clk_hw **best_parent,
> +		       unsigned long *best_parent_rate,
> +		       u32 *div_val)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	unsigned int parent_num = clk_hw_get_num_parents(hw);
> +	struct ccu_div_config *div = &mix->div;
> +	u32 div_max = 1 << div->width;
> +	unsigned long best_rate = 0;
> +
> +	for (int i = 0; i < parent_num; i++) {
> +		struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
> +		unsigned long parent_rate;
> +
> +		if (!parent)
> +			continue;
> +
> +		parent_rate = clk_hw_get_rate(parent);
> +
> +		for (int j = 1; j <= div_max; j++) {
> +			unsigned long tmp = DIV_ROUND_UP_ULL(parent_rate, j);
> +
> +			if (abs(tmp - rate) < abs(best_rate - rate)) {
> +				best_rate = tmp;
> +
> +				if (div_val)
> +					*div_val = j - 1;
> +
> +				if (best_parent) {
> +					*best_parent      = parent;
> +					*best_parent_rate = parent_rate;
> +				}
> +			}
> +		}
> +	}
> +
> +	return best_rate;
> +}
> +
> +static int ccu_mix_determine_rate(struct clk_hw *hw,
> +				  struct clk_rate_request *req)
> +{
> +	req->rate = ccu_mix_calc_best_rate(hw, req->rate,
> +					   &req->best_parent_hw,
> +					   &req->best_parent_rate,
> +					   NULL);
> +	return 0;
> +}
> +
> +static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_div_config *div = &mix->div;
> +	int ret = 0, tmp = 0;
> +	u32 current_div, target_div;
> +
> +	ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
> +
> +	ccu_read(ctrl, common, &tmp);
> +
> +	current_div = tmp >> div->shift;
> +	current_div &= (1 << div->width) - 1;
> +
> +	if (current_div == target_div)
> +		return 0;
> +
> +	tmp = GENMASK(div->width + div->shift - 1, div->shift);
> +
> +	ccu_update(ctrl, common, tmp, target_div << div->shift);
> +
> +	if (common->reg_fc)
> +		ret = ccu_mix_trigger_fc(hw);
> +
> +	return ret;
> +}
> +
> +static u8 ccu_mux_get_parent(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_mux_config *mux = &mix->mux;
> +	u32 reg;
> +	u8 parent;
> +
> +	ccu_read(ctrl, common, &reg);
> +
> +	parent = reg >> mux->shift;
> +	parent &= (1 << mux->width) - 1;
> +
> +	return parent;
> +}
> +
> +static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_mux_config *mux = &mix->mux;
> +	int ret = 0;
> +	u32 mask;
> +
> +	mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
> +
> +	ccu_update(ctrl, common, mask, index << mux->shift);
> +
> +	if (common->reg_fc)
> +		ret = ccu_mix_trigger_fc(hw);
> +
> +	return ret;
> +}
> +
> +const struct clk_ops spacemit_ccu_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +};
> +
> +const struct clk_ops spacemit_ccu_factor_ops = {
> +	.round_rate	= ccu_factor_round_rate,
> +	.recalc_rate	= ccu_factor_recalc_rate,
> +	.set_rate	= ccu_factor_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_mux_ops = {
> +	.determine_rate = ccu_mix_determine_rate,
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_ops = {
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_gate_factor_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.round_rate	= ccu_factor_round_rate,
> +	.recalc_rate	= ccu_factor_recalc_rate,
> +	.set_rate	= ccu_factor_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_mux_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_mux_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_mux_ops = {
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h
> new file mode 100644
> index 000000000000..a3aa292d073d
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_mix.h
> @@ -0,0 +1,246 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_MIX_H_
> +#define _CCU_MIX_H_
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_common.h"
> +
> +struct ccu_gate_config {
> +	u32 mask;
> +};
> +
> +struct ccu_factor_config {
> +	u32 div;
> +	u32 mul;
> +};
> +
> +struct ccu_mux_config {
> +	u8 shift;
> +	u8 width;
> +};
> +
> +struct ccu_div_config {
> +	u8 shift;
> +	u8 width;
> +};
> +
> +struct ccu_mix {
> +	struct ccu_factor_config factor;
> +	struct ccu_gate_config gate;
> +	struct ccu_div_config div;
> +	struct ccu_mux_config mux;
> +	struct ccu_common common;
> +};
> +
> +#define CCU_GATE_INIT(_mask) { .mask = _mask }
> +#define CCU_FACTOR_INIT(_div, _mul) { .div = _div, .mul = _mul }
> +#define CCU_MUX_INIT(_shift, _width) { .shift = _shift, .width = _width }
> +#define CCU_DIV_INIT(_shift, _width) { .shift = _shift, .width = _width }
> +
> +#define CCU_PARENT_HW(_parent)		{ .hw = &_parent.common.hw }
> +#define CCU_PARENT_NAME(_name)		{ .fw_name = #_name }
> +
> +#define CCU_MIX_INITHW(_name, _parent, _ops, _flags)			\
> +	(&(struct clk_init_data) {					\
> +		.flags		= _flags,				\
> +		.name		= #_name,				\
> +		.parent_data	= (const struct clk_parent_data[])	\
> +					{ _parent },			\
> +		.num_parents	= 1,					\
> +		.ops		= &_ops,				\
> +	})
> +
> +#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags)		\
> +	CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
> +
> +#define CCU_GATE_DEFINE(_name, _parent, _reg, _gate_mask, _flags)		\
> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.common	= {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_gate_ops, _flags),	\
> +	}									\
> +}
> +
> +#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul)				\
> +struct ccu_mix _name = {							\
> +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
> +	.common = {								\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_factor_ops, 0),		\
> +	}									\
> +}
> +
> +#define CCU_MUX_DEFINE(_name, _parents, _reg, _shift, _width, _flags)		\
> +struct ccu_mix _name = {							\
> +	.mux	= CCU_MUX_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_mux_ops,	_flags),\
> +	}									\
> +}
> +
> +#define CCU_DIV_DEFINE(_name, _parent, _reg, _shift, _width, _flags)		\
> +struct ccu_mix _name = {							\
> +	.div	= CCU_DIV_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_div_ops, _flags)		\
> +	}									\
> +}
> +
> +#define CCU_GATE_FACTOR_DEFINE(_name, _parent,					\
> +			       _reg,						\
> +			       _gate_mask,					\
> +			       _div, _mul,					\
> +			       _flags)						\
> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_gate_factor_ops, _flags)	\
> +	}									\
> +}
> +
> +#define CCU_MUX_GATE_DEFINE(_name, _parents,					\
> +			    _reg,						\
> +			    _shift, _width,					\
> +			    _gate_mask,						\
> +			    _flags)						\
> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.mux	= CCU_MUX_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_mux_gate_ops,	\
> +						  _flags),			\
> +	}									\
> +}
> +
> +#define CCU_DIV_GATE_DEFINE(_name, _parent,					\
> +			    _reg,						\
> +			    _shift, _width,					\
> +			    _gate_mask,						\
> +			    _flags)						\
> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.div	= CCU_DIV_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_div_gate_ops, _flags),	\
> +	}									\
> +}
> +
> +#define CCU_DIV_MUX_GATE_DEFINE(_name, _parents,				\
> +				_reg_ctrl,					\
> +				_mshift, _mwidth, _muxshift, _muxwidth,		\
> +				_gate_mask,					\
> +				_flags)						\
> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common	= {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_div_mux_gate_ops,\
> +						  _flags),			\
> +	},									\
> +}
> +
> +#define CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents,			\
> +					 _reg_ctrl, _reg_fc,			\
> +					 _mshift, _mwidth,			\
> +					 _fc,					\
> +					 _muxshift, _muxwidth,			\
> +					 _gate_mask,				\
> +					 _flags)				\
> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common = {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.reg_fc		= _reg_fc,					\
> +		.fc		= _fc,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_div_mux_gate_ops,\
> +						  _flags),			\
> +	},									\
> +}
> +
> +#define CCU_DIV_FC_MUX_GATE_DEFINE(_name, _parents,				\
> +				   _reg_ctrl,					\
> +				   _mshift, _mwidth,				\
> +				   _fc,						\
> +				   _muxshift, _muxwidth,			\
> +				   _gate_mask, _flags)		\
> +CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents, _reg_ctrl, _reg_ctrl,		\
> +				 _mshift, _mwidth, _fc, _muxshift, _muxwidth,		\
> +				 _gate_mask, _flags)
> +
> +#define CCU_DIV_FC_MUX_DEFINE(_name, _parents,					\
> +			      _reg_ctrl,					\
> +			      _mshift, _mwidth,					\
> +			      _fc,						\
> +			      _muxshift, _muxwidth,				\
> +			      _flags)						\
> +struct ccu_mix _name = {							\
> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common = {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.reg_fc		= _reg_ctrl,					\
> +		.fc		= _fc,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_div_mux_ops,	\
> +						  _flags),			\
> +	},									\
> +}
> +
> +#define CCU_MUX_FC_DEFINE(_name, _parents,					\
> +			  _reg_ctrl,						\
> +			  _fc,							\
> +			  _muxshift, _muxwidth,					\
> +			  _flags)						\
> +struct ccu_mix _name = {							\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common = {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.reg_fc		= _reg_ctrl,					\
> +		.fc		= _fc,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_mux_ops,	_flags)	\
> +	},									\
> +}
> +
> +static inline struct ccu_mix *hw_to_ccu_mix(struct clk_hw *hw)
> +{
> +	struct ccu_common *common = hw_to_ccu_common(hw);
> +
> +	return container_of(common, struct ccu_mix, common);
> +}
> +
> +extern const struct clk_ops spacemit_ccu_gate_ops, spacemit_ccu_factor_ops;
> +extern const struct clk_ops spacemit_ccu_mux_ops, spacemit_ccu_div_ops;
> +
> +extern const struct clk_ops spacemit_ccu_gate_factor_ops;
> +extern const struct clk_ops spacemit_ccu_div_gate_ops;
> +extern const struct clk_ops spacemit_ccu_mux_gate_ops;
> +extern const struct clk_ops spacemit_ccu_div_mux_ops;
> +
> +extern const struct clk_ops spacemit_ccu_div_mux_gate_ops;
> +#endif /* _CCU_DIV_H_ */
> diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
> new file mode 100644
> index 000000000000..9df2149f6c98
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_pll.c
> @@ -0,0 +1,146 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit clock type pll
> + *
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/regmap.h>
> +
> +#include "ccu_common.h"
> +#include "ccu_pll.h"
> +
> +#define PLL_DELAY_TIME	3000
> +
> +#define PLL_SWCR3_EN	BIT(31)
> +
> +static int ccu_pll_is_enabled(struct clk_hw *hw)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	u32 tmp;
> +
> +	ccu_read(swcr3, &p->common, &tmp);
> +
> +	return tmp & PLL_SWCR3_EN;
> +}
> +
> +/* frequency unit Mhz, return pll vco freq */
> +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
> +{
> +	const struct ccu_pll_rate_tbl *pll_rate_table;
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +	u32 swcr1, swcr3, size;
> +	int i;
> +
> +	ccu_read(swcr1, common, &swcr1);
> +	ccu_read(swcr3, common, &swcr3);
> +	swcr3 &= ~PLL_SWCR3_EN;
> +
> +	pll_rate_table = p->pll.rate_tbl;
> +	size = p->pll.tbl_size;
> +
> +	for (i = 0; i < size; i++) {
> +		if (pll_rate_table[i].swcr1 == swcr1 &&
> +		    pll_rate_table[i].swcr3 == swcr3)
> +			return pll_rate_table[i].rate;
> +	}
> +
> +	WARN_ON_ONCE(1);
> +
> +	return 0;
> +}
> +
> +static int ccu_pll_enable(struct clk_hw *hw)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +	unsigned int tmp;
> +	int ret;
> +
> +	if (ccu_pll_is_enabled(hw))
> +		return 0;
> +
> +	ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN);
> +
> +	/* check lock status */
> +	ret = regmap_read_poll_timeout_atomic(common->lock_regmap,
> +					      p->pll.reg_lock,
> +					      tmp,
> +					      tmp & p->pll.lock_enable_bit,
> +					      5, PLL_DELAY_TIME);
> +
> +	return ret;
> +}
> +
> +static void ccu_pll_disable(struct clk_hw *hw)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +
> +	ccu_update(swcr3, common, PLL_SWCR3_EN, 0);
> +}
> +
> +/*
> + * PLLs must be gated before changing rate, which is ensured by
> + * flag CLK_SET_RATE_GATE.
> + */
> +static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +	struct ccu_pll_config *params = &p->pll;
> +	const struct ccu_pll_rate_tbl *entry = NULL;
> +	int i;
> +
> +	for (i = 0; i < params->tbl_size; i++) {
> +		if (rate == params->rate_tbl[i].rate) {
> +			entry = &params->rate_tbl[i];
> +			break;
> +		}
> +	}
> +
> +	if (WARN_ON_ONCE(!entry))
> +		return -EINVAL;
> +
> +	ccu_update(swcr1, common, entry->swcr1, entry->swcr1);
> +	ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3);
> +
> +	return 0;
> +}
> +
> +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	return ccu_pll_get_vco_freq(hw);
> +}
> +
> +static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long *prate)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_pll_config *params = &p->pll;
> +	unsigned int i;
> +
> +	for (i = 0; i < params->tbl_size; i++) {
> +		if (params->rate_tbl[i].rate > rate) {
> +			i--;
> +			break;
> +		}
> +	}
> +
> +	return rate;
> +}
> +
> +const struct clk_ops spacemit_ccu_pll_ops = {
> +	.enable		= ccu_pll_enable,
> +	.disable	= ccu_pll_disable,
> +	.set_rate	= ccu_pll_set_rate,
> +	.recalc_rate	= ccu_pll_recalc_rate,
> +	.round_rate	= ccu_pll_round_rate,
> +	.is_enabled	= ccu_pll_is_enabled,
> +};
> +
> diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h
> new file mode 100644
> index 000000000000..c6a3a5cce995
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_pll.h
> @@ -0,0 +1,76 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_PLL_H_
> +#define _CCU_PLL_H_
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_common.h"
> +
> +struct ccu_pll_rate_tbl {
> +	unsigned long rate;
> +	u32 swcr1;
> +	u32 swcr3;
> +};
> +
> +struct ccu_pll_config {
> +	const struct ccu_pll_rate_tbl *rate_tbl;
> +	u32 tbl_size;
> +	u32 reg_lock;
> +	u32 lock_enable_bit;
> +};
> +
> +#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
> +	{									\
> +		.rate	= _rate,							\
> +		.swcr1	= _swcr1,						\
> +		.swcr3	= _swcr3,						\
> +	}
> +
> +struct ccu_pll {
> +	struct ccu_pll_config	pll;
> +	struct ccu_common	common;
> +};
> +
> +#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \
> +	{									\
> +		.rate_tbl	 = _table,					\
> +		.tbl_size	 = ARRAY_SIZE(_table),				\
> +		.reg_lock	 = (_reg_lock),					\
> +		.lock_enable_bit = (_lock_enable_bit),				\
> +	}
> +
> +#define CCU_PLL_HWINIT(_name, _flags)						\
> +	(&(struct clk_init_data) {						\
> +		.name		= #_name,					\
> +		.ops		= &spacemit_ccu_pll_ops,			\
> +		.parent_data	= &(struct clk_parent_data) { .index = 0 },	\
> +		.num_parents	= 1,						\
> +		.flags		= _flags,					\
> +	})
> +
> +#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3,			\
> +		       _reg_lock, _lock_enable_bit, _flags)			\
> +	struct ccu_pll _name = {						\
> +		.pll	= CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit),	\
> +		.common = {							\
> +			.reg_swcr1	= _reg_swcr1,				\
> +			.reg_swcr3	= _reg_swcr3,				\
> +			.hw.init	= CCU_PLL_HWINIT(_name, _flags)		\
> +		}								\
> +	}
> +
> +static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
> +{
> +	struct ccu_common *common = hw_to_ccu_common(hw);
> +
> +	return container_of(common, struct ccu_pll, common);
> +}
> +
> +extern const struct clk_ops spacemit_ccu_pll_ops;
> +
> +#endif
> -- 
> 2.48.1
> 
> 

-- 
Yixun Lan (dlan)
Gentoo Linux Developer
GPG Key ID AABEFD55

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1
  2025-03-06 17:57 ` [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1 Haylen Chu
@ 2025-03-07  1:55   ` Inochi Amaoto
  2025-03-07  6:28     ` Haylen Chu
  0 siblings, 1 reply; 26+ messages in thread
From: Inochi Amaoto @ 2025-03-07  1:55 UTC (permalink / raw)
  To: Haylen Chu, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang

On Thu, Mar 06, 2025 at 05:57:51PM +0000, Haylen Chu wrote:
> Describe the PLL and system controllers that're capable of generating
> clock signals in the devicetree.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> ---
>  arch/riscv/boot/dts/spacemit/k1.dtsi | 79 ++++++++++++++++++++++++++++
>  1 file changed, 79 insertions(+)
> 
> diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi
> index c670ebf8fa12..09a9100986b1 100644
> --- a/arch/riscv/boot/dts/spacemit/k1.dtsi
> +++ b/arch/riscv/boot/dts/spacemit/k1.dtsi
> @@ -3,6 +3,8 @@
>   * Copyright (C) 2024 Yangyu Chen <cyy@cyyself.name>
>   */
>  
> +#include <dt-bindings/clock/spacemit,k1-ccu.h>
> +
>  /dts-v1/;
>  / {
>  	#address-cells = <2>;
> @@ -306,6 +308,40 @@ cluster1_l2_cache: l2-cache1 {
>  		};
>  	};
>  
> +	clocks {

> +		#address-cells = <0x2>;
> +		#size-cells = <0x2>;
> +		ranges;

why setting this?

> +
> +		vctcxo_1m: clock-1m {
> +			compatible = "fixed-clock";

> +			clock-frequency = <1000000>;

Should the frequency this move to the board file?
I do not think these clock are in the soc.
This applys to all clock below.

> +			clock-output-names = "vctcxo_1m";
> +			#clock-cells = <0>;
> +		};
> +
> +		vctcxo_24m: clock-24m {
> +			compatible = "fixed-clock";
> +			clock-frequency = <24000000>;
> +			clock-output-names = "vctcxo_24m";
> +			#clock-cells = <0>;
> +		};
> +
> +		vctcxo_3m: clock-3m {
> +			compatible = "fixed-clock";
> +			clock-frequency = <3000000>;
> +			clock-output-names = "vctcxo_3m";
> +			#clock-cells = <0>;
> +		};
> +
> +		osc_32k: clock-32k {
> +			compatible = "fixed-clock";
> +			clock-frequency = <32000>;
> +			clock-output-names = "osc_32k";
> +			#clock-cells = <0>;
> +		};
> +	};
> +
>  	soc {
>  		compatible = "simple-bus";
>  		interrupt-parent = <&plic>;
> @@ -314,6 +350,17 @@ soc {
>  		dma-noncoherent;
>  		ranges;
>  
> +		syscon_apbc: system-control@d4015000 {
> +			compatible = "spacemit,k1-syscon-apbc";
> +			reg = <0x0 0xd4015000 0x0 0x1000>;
> +			clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>,
> +				 <&vctcxo_24m>;
> +			clock-names = "osc", "vctcxo_1m", "vctcxo_3m",
> +				      "vctcxo_24m";
> +			#clock-cells = <1>;
> +			#reset-cells = <1>;
> +		};
> +
>  		uart0: serial@d4017000 {
>  			compatible = "spacemit,k1-uart", "intel,xscale-uart";
>  			reg = <0x0 0xd4017000 0x0 0x100>;
> @@ -409,6 +456,38 @@ pinctrl: pinctrl@d401e000 {
>  			reg = <0x0 0xd401e000 0x0 0x400>;
>  		};
>  
> +		syscon_mpmu: system-controller@d4050000 {
> +			compatible = "spacemit,k1-syscon-mpmu";
> +			reg = <0x0 0xd4050000 0x0 0x209c>;
> +			clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>,
> +				 <&vctcxo_24m>;
> +			clock-names = "osc", "vctcxo_1m", "vctcxo_3m",
> +				      "vctcxo_24m";
> +			#clock-cells = <1>;
> +			#power-domain-cells = <1>;
> +			#reset-cells = <1>;
> +		};
> +
> +		pll: system-control@d4090000 {
> +			compatible = "spacemit,k1-pll";
> +			reg = <0x0 0xd4090000 0x0 0x1000>;
> +			clocks = <&vctcxo_24m>;
> +			spacemit,mpmu = <&syscon_mpmu>;
> +			#clock-cells = <1>;
> +		};
> +
> +		syscon_apmu: system-control@d4282800 {
> +			compatible = "spacemit,k1-syscon-apmu";
> +			reg = <0x0 0xd4282800 0x0 0x400>;
> +			clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>,
> +				 <&vctcxo_24m>;
> +			clock-names = "osc", "vctcxo_1m", "vctcxo_3m",
> +				      "vctcxo_24m";
> +			#clock-cells = <1>;
> +			#power-domain-cells = <1>;
> +			#reset-cells = <1>;
> +		};
> +
>  		plic: interrupt-controller@e0000000 {
>  			compatible = "spacemit,k1-plic", "sifive,plic-1.0.0";
>  			reg = <0x0 0xe0000000 0x0 0x4000000>;
> -- 
> 2.48.1
> 

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1
  2025-03-07  1:55   ` Inochi Amaoto
@ 2025-03-07  6:28     ` Haylen Chu
  0 siblings, 0 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-07  6:28 UTC (permalink / raw)
  To: Inochi Amaoto, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang

On Fri, Mar 07, 2025 at 09:55:15AM +0800, Inochi Amaoto wrote:
> On Thu, Mar 06, 2025 at 05:57:51PM +0000, Haylen Chu wrote:
> > Describe the PLL and system controllers that're capable of generating
> > clock signals in the devicetree.
> > 
> > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> > ---
> >  arch/riscv/boot/dts/spacemit/k1.dtsi | 79 ++++++++++++++++++++++++++++
> >  1 file changed, 79 insertions(+)
> > 
> > diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi
> > index c670ebf8fa12..09a9100986b1 100644
> > --- a/arch/riscv/boot/dts/spacemit/k1.dtsi
> > +++ b/arch/riscv/boot/dts/spacemit/k1.dtsi
> > @@ -3,6 +3,8 @@
> >   * Copyright (C) 2024 Yangyu Chen <cyy@cyyself.name>
> >   */
> >  
> > +#include <dt-bindings/clock/spacemit,k1-ccu.h>
> > +
> >  /dts-v1/;
> >  / {
> >  	#address-cells = <2>;
> > @@ -306,6 +308,40 @@ cluster1_l2_cache: l2-cache1 {
> >  		};
> >  	};
> >  
> > +	clocks {
> 
> > +		#address-cells = <0x2>;
> > +		#size-cells = <0x2>;
> > +		ranges;
> 
> why setting this?

I just noticed these unnecessary properties after sending the series,
will drop them, thanks.

> > +
> > +		vctcxo_1m: clock-1m {
> > +			compatible = "fixed-clock";
> 
> > +			clock-frequency = <1000000>;
> 
> Should the frequency this move to the board file?
> I do not think these clock are in the soc.
> This applys to all clock below.

This should be the special case described in the dts-coding-style
documentation,

> A partial exception is a common external reference SoC input clock,
> which could be coded as a fixed-clock in the SoC DTSI with its
> frequency provided by each board DTS.[1]

These four external clocks are essential for K1 SoC to operate as shown
in the clock tree diagram[2]. I think they fit the exception.

Furthermore, the SoC expects these four clocks in fixed rates, thus I
don't see a reason not to include the clock-frequency property here.
Please correct me if I get something wrong.

> > +			clock-output-names = "vctcxo_1m";
> > +			#clock-cells = <0>;
> > +		};
> > +
> > +		vctcxo_24m: clock-24m {
> > +			compatible = "fixed-clock";
> > +			clock-frequency = <24000000>;
> > +			clock-output-names = "vctcxo_24m";
> > +			#clock-cells = <0>;
> > +		};
> > +
> > +		vctcxo_3m: clock-3m {
> > +			compatible = "fixed-clock";
> > +			clock-frequency = <3000000>;
> > +			clock-output-names = "vctcxo_3m";
> > +			#clock-cells = <0>;
> > +		};
> > +
> > +		osc_32k: clock-32k {
> > +			compatible = "fixed-clock";
> > +			clock-frequency = <32000>;
> > +			clock-output-names = "osc_32k";
> > +			#clock-cells = <0>;
> > +		};
> > +	};
> > +
> >  	soc {
> >  		compatible = "simple-bus";
> >  		interrupt-parent = <&plic>;

Thanks,
Haylen Chu

[1]: https://elixir.bootlin.com/linux/v6.11-rc1/source/Documentation/devicetree/bindings/dts-coding-style.rst#L196
[2]: https://developer.spacemit.com/resource/file/images?fileName=E9VPb63n8o1heox2KXHcgWiVnWh.png

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 4/5] clk: spacemit: k1: Add TWSI8 bus and function clocks
  2025-03-06 17:57 ` [PATCH v5 4/5] clk: spacemit: k1: Add TWSI8 bus and function clocks Haylen Chu
@ 2025-03-07  6:30   ` Haylen Chu
  0 siblings, 0 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-07  6:30 UTC (permalink / raw)
  To: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang

On Thu, Mar 06, 2025 at 05:57:50PM +0000, Haylen Chu wrote:
> The control register for TWSI8 clocks, APBC_TWSI8_CLK_RST, contains mux
> selection bits, reset assertion bit and enable bits for function and bus
> clocks. It has a quirk that reading always results in zero.
> 
> As a workaround, let's hardcode the mux value as zero to select
> pll1_d78_31p5 as parent and treat twsi8_clk as a gate, whose enable mask
> is combined from the real bus and function clocks to avoid the
> write-only register being shared between two clk_hws, in which case
> updates of one clk_hw zero the other's bits.
> 
> With a 1:1 factor serving as placeholder for the bus clock, the I2C-8
> controller could be brought up, which is essential for boards attaching
> power-management chips to it.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> ---
>  drivers/clk/spacemit/ccu-k1.c | 6 ++++++
>  1 file changed, 6 insertions(+)
> 
> diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
> index 5974a0a1b5f6..44db48ae7131 100644
> --- a/drivers/clk/spacemit/ccu-k1.c
> +++ b/drivers/clk/spacemit/ccu-k1.c
> @@ -558,6 +558,10 @@ static CCU_MUX_GATE_DEFINE(twsi7_clk, twsi_parents,
>  			   APBC_TWSI7_CLK_RST,
>  			   4, 3, BIT(1),
>  			   0);
> +static CCU_GATE_DEFINE(twsi8_clk, CCU_PARENT_HW(pll1_d78_31p5),
> +		       APBC_TWSI8_CLK_RST,
> +		       BIT(1) | BIT(0),
> +		       0);
>  
>  static const struct clk_parent_data timer_parents[] = {
>  	CCU_PARENT_HW(pll1_d192_12p8),
> @@ -795,6 +799,8 @@ static CCU_GATE_DEFINE(twsi7_bus_clk, CCU_PARENT_HW(apb_clk),
>  		       APBC_TWSI7_CLK_RST,
>  		       BIT(0),
>  		       0);
> +static CCU_FACTOR_DEFINE(twsi8_bus_clk, CCU_PARENT_HW(apb_clk),
> +			 1, 1);
>  
>  static CCU_GATE_DEFINE(timers1_bus_clk, CCU_PARENT_HW(apb_clk),
>  		       APBC_TIMERS1_CLK_RST,
> -- 
> 2.48.1
> 

Oops, I don't split out the twsi8-related definitions completely from
PATCH 3, causing building errors with only PATCH 3 applied. Will fix it
in the next version.

Best regards,
Haylen Chu

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll
  2025-03-07  0:29   ` Yixun Lan
@ 2025-03-07  6:34     ` Haylen Chu
  2025-03-07  8:20       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 26+ messages in thread
From: Haylen Chu @ 2025-03-07  6:34 UTC (permalink / raw)
  To: Yixun Lan
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

On Fri, Mar 07, 2025 at 12:29:43AM +0000, Yixun Lan wrote:
> Hi Haylen:
> 
> On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> > Add definition for the PLL found on Spacemit K1 SoC, which takes the
> > external 24MHz oscillator as input and generates clocks in various
> > frequencies for the system.
> > 
> > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> > ---
> >  .../bindings/clock/spacemit,k1-pll.yaml       | 50 +++++++++++++++++++
> >  include/dt-bindings/clock/spacemit,k1-ccu.h   | 37 ++++++++++++++
> >  2 files changed, 87 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml b/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
> > new file mode 100644
> > index 000000000000..23d7aa1bc573
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/clock/spacemit,k1-pll.yaml
> > @@ -0,0 +1,50 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/clock/spacemit,k1-pll.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Spacemit K1 PLL
> > +
> > +maintainers:
> > +  - Haylen Chu <heylenay@4d2.org>
> > +
> > +properties:
> > +  compatible:
> > +    const: spacemit,k1-pll
> > +
> > +  reg:
> > +    maxItems: 1
> > +
> > +  clocks:
> > +    description: External 24MHz oscillator
> > +
> > +  spacemit,mpmu:
> how about naming it as "spacemit,mpmu-syscon" explicitly?
> to indicate this is a syscon phandle, it's more readable

It's okay but I'm not sure whether this helps a lot... will wait for the
maintainer to decide the name.

> > +    $ref: /schemas/types.yaml#/definitions/phandle
> > +    description:
> > +      Phandle to the "Main PMU (MPMU)" syscon. It is used to check PLL
> > +      lock status.
> > +
> > +  "#clock-cells":
> > +    const: 1
> > +    description:
> > +      See <dt-bindings/clock/spacemit,k1-ccu.h> for valid indices.
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - clocks
> > +  - spacemit,mpmu
> > +  - "#clock-cells"
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    clock-controller@d4090000 {
> > +        compatible = "spacemit,k1-pll";
> > +        reg = <0xd4090000 0x1000>;
> > +        clocks = <&vctcxo_24m>;
> > +        spacemit,mpmu = <&sysctl_mpmu>;
> > +        #clock-cells = <1>;
> > +    };
> -- 

...

> Yixun Lan (dlan)
> Gentoo Linux Developer
> GPG Key ID AABEFD55

Best regards,
Haylen Chu

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-07  0:51   ` Yixun Lan
@ 2025-03-07  6:42     ` Haylen Chu
  2025-03-07  8:26       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 26+ messages in thread
From: Haylen Chu @ 2025-03-07  6:42 UTC (permalink / raw)
  To: Yixun Lan
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

On Fri, Mar 07, 2025 at 12:51:49AM +0000, Yixun Lan wrote:
> On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> > The clock tree of K1 SoC contains three main types of clock hardware
> > (PLL/DDN/MIX) and has control registers split into several multifunction
> > devices: APBS (PLLs), MPMU, APBC and APMU.
> > 
> > All register operations are done through regmap to ensure atomiciy
> > between concurrent operations of clock driver and reset,
> > power-domain driver that will be introduced in the future.
> > 
> > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> > ---
> >  drivers/clk/Kconfig               |    1 +
> >  drivers/clk/Makefile              |    1 +
> >  drivers/clk/spacemit/Kconfig      |   20 +
> >  drivers/clk/spacemit/Makefile     |    5 +
> >  drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
> >  drivers/clk/spacemit/ccu_common.h |   47 +
> >  drivers/clk/spacemit/ccu_ddn.c    |   80 ++
> >  drivers/clk/spacemit/ccu_ddn.h    |   48 +
> >  drivers/clk/spacemit/ccu_mix.c    |  284 +++++
> >  drivers/clk/spacemit/ccu_mix.h    |  246 +++++
> >  drivers/clk/spacemit/ccu_pll.c    |  146 +++
> >  drivers/clk/spacemit/ccu_pll.h    |   76 ++
> >  12 files changed, 2668 insertions(+)
> >  create mode 100644 drivers/clk/spacemit/Kconfig
> >  create mode 100644 drivers/clk/spacemit/Makefile
> >  create mode 100644 drivers/clk/spacemit/ccu-k1.c
> >  create mode 100644 drivers/clk/spacemit/ccu_common.h
> >  create mode 100644 drivers/clk/spacemit/ccu_ddn.c
> >  create mode 100644 drivers/clk/spacemit/ccu_ddn.h
> >  create mode 100644 drivers/clk/spacemit/ccu_mix.c
> >  create mode 100644 drivers/clk/spacemit/ccu_mix.h
> >  create mode 100644 drivers/clk/spacemit/ccu_pll.c
> >  create mode 100644 drivers/clk/spacemit/ccu_pll.h
> > 
> ..
> > +static int k1_ccu_probe(struct platform_device *pdev)
> > +{
> > +	struct regmap *base_regmap, *lock_regmap = NULL;
> > +	struct device *dev = &pdev->dev;
> > +	int ret;
> > +
> > +	base_regmap = device_node_to_regmap(dev->of_node);
> > +	if (IS_ERR(base_regmap))
> > +		return dev_err_probe(dev, PTR_ERR(base_regmap),
> > +				     "failed to get regmap\n");
> > +
> > +	if (of_device_is_compatible(dev->of_node, "spacemit,k1-pll")) {
> ..
> > +		struct device_node *mpmu = of_parse_phandle(dev->of_node,
> > +							    "spacemit,mpmu", 0);
> > +		if (!mpmu)
> > +			return dev_err_probe(dev, -ENODEV,
> > +					     "Cannot parse MPMU region\n");
> > +
> > +		lock_regmap = device_node_to_regmap(mpmu);
> > +		of_node_put(mpmu);
> > +
> you can simplify above with syscon_regmap_lookup_by_phandle(), which
> would save a few lines
> 
> or further, just call syscon_regmap_lookup_by_compatible()? then
> won't be necessary to introduce the "spacemit,mpmu" property..
> 

These syscon_* functions differ a little from device_node_to_regmap():
they get and enable the first item in "clocks" property when
instantiating a regmap, which isn't desired for a clock controller.

> > +		if (IS_ERR(lock_regmap))
> > +			return dev_err_probe(dev, PTR_ERR(lock_regmap),
> > +					     "failed to get lock regmap\n");
> > +	}
> > +
> > +	ret = spacemit_ccu_register(dev, base_regmap, lock_regmap,
> > +				    of_device_get_match_data(dev));
> > +	if (ret)
> > +		return dev_err_probe(dev, ret, "failed to register clocks\n");
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id of_k1_ccu_match[] = {
> > +	{
> > +		.compatible	= "spacemit,k1-pll",
> > +		.data		= k1_ccu_apbs_clks,
> > +	},
> > +	{
> > +		.compatible	= "spacemit,k1-syscon-mpmu",
> > +		.data		= k1_ccu_mpmu_clks,
> > +	},
> > +	{
> > +		.compatible	= "spacemit,k1-syscon-apbc",
> > +		.data		= k1_ccu_apbc_clks,
> > +	},
> > +	{
> > +		.compatible	= "spacemit,k1-syscon-apmu",
> > +		.data		= k1_ccu_apmu_clks,
> > +	},
> > +	{ }
> > +};
> > +MODULE_DEVICE_TABLE(of, of_k1_ccu_match);
> > +
> > +static struct platform_driver k1_ccu_driver = {
> > +	.driver = {
> > +		.name		= "spacemit,k1-ccu",
> > +		.of_match_table = of_k1_ccu_match,
> > +	},
> > +	.probe	= k1_ccu_probe,
> > +};
> > +module_platform_driver(k1_ccu_driver);
> > +
> > +MODULE_DESCRIPTION("Spacemit K1 CCU driver");
> > +MODULE_AUTHOR("Haylen Chu <heylenay@4d2.org>");
> > +MODULE_LICENSE("GPL");
> 

...

> -- 
> Yixun Lan (dlan)
> Gentoo Linux Developer
> GPG Key ID AABEFD55

Best regards,
Haylen Chu

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon
  2025-03-06 17:57 ` [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu
@ 2025-03-07  8:16   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-07  8:16 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk,
	devicetree, linux-kernel, spacemit, Inochi Amaoto, Chen Wang,
	Jisheng Zhang, Meng Zhang

On Thu, Mar 06, 2025 at 05:57:47PM +0000, Haylen Chu wrote:
> Document APMU, MPMU and APBC syscons found on Spacemit K1 SoC, which are
> capable of generating clock and reset signals. Additionally, APMU and MPMU
> manage power domains.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> ---
>  .../soc/spacemit/spacemit,k1-syscon.yaml      |  80 +++++++
>  include/dt-bindings/clock/spacemit,k1-ccu.h   | 210 ++++++++++++++++++

Filename matching binding, so spacemit,k1-syscon.h
(or vice-versa)

With this fixed:

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

Best regards,
Krzysztof


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll
  2025-03-06 17:57 ` [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll Haylen Chu
  2025-03-07  0:29   ` Yixun Lan
@ 2025-03-07  8:19   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-07  8:19 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk,
	devicetree, linux-kernel, spacemit, Inochi Amaoto, Chen Wang,
	Jisheng Zhang, Meng Zhang

On Thu, Mar 06, 2025 at 05:57:48PM +0000, Haylen Chu wrote:
> Add definition for the PLL found on Spacemit K1 SoC, which takes the
> external 24MHz oscillator as input and generates clocks in various
> frequencies for the system.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>

Best regards,
Krzysztof


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll
  2025-03-07  6:34     ` Haylen Chu
@ 2025-03-07  8:20       ` Krzysztof Kozlowski
  0 siblings, 0 replies; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-07  8:20 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Yixun Lan, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, linux-riscv,
	linux-clk, devicetree, linux-kernel, spacemit, Inochi Amaoto,
	Chen Wang, Jisheng Zhang, Meng Zhang

On Fri, Mar 07, 2025 at 06:34:47AM +0000, Haylen Chu wrote:
> > > +properties:
> > > +  compatible:
> > > +    const: spacemit,k1-pll
> > > +
> > > +  reg:
> > > +    maxItems: 1
> > > +
> > > +  clocks:
> > > +    description: External 24MHz oscillator
> > > +
> > > +  spacemit,mpmu:
> > how about naming it as "spacemit,mpmu-syscon" explicitly?
> > to indicate this is a syscon phandle, it's more readable
> 
> It's okay but I'm not sure whether this helps a lot... will wait for the
> maintainer to decide the name.

Can mpmu be not a syscon phandle? And what exactly means "syscon" here
that it brings additional information?

Best regards,
Krzysztof


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-07  6:42     ` Haylen Chu
@ 2025-03-07  8:26       ` Krzysztof Kozlowski
  0 siblings, 0 replies; 26+ messages in thread
From: Krzysztof Kozlowski @ 2025-03-07  8:26 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Yixun Lan, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, linux-riscv,
	linux-clk, devicetree, linux-kernel, spacemit, Inochi Amaoto,
	Chen Wang, Jisheng Zhang, Meng Zhang

On Fri, Mar 07, 2025 at 06:42:02AM +0000, Haylen Chu wrote:
> > > +static int k1_ccu_probe(struct platform_device *pdev)
> > > +{
> > > +	struct regmap *base_regmap, *lock_regmap = NULL;
> > > +	struct device *dev = &pdev->dev;
> > > +	int ret;
> > > +
> > > +	base_regmap = device_node_to_regmap(dev->of_node);
> > > +	if (IS_ERR(base_regmap))
> > > +		return dev_err_probe(dev, PTR_ERR(base_regmap),
> > > +				     "failed to get regmap\n");
> > > +
> > > +	if (of_device_is_compatible(dev->of_node, "spacemit,k1-pll")) {
> > ..
> > > +		struct device_node *mpmu = of_parse_phandle(dev->of_node,
> > > +							    "spacemit,mpmu", 0);
> > > +		if (!mpmu)
> > > +			return dev_err_probe(dev, -ENODEV,
> > > +					     "Cannot parse MPMU region\n");
> > > +
> > > +		lock_regmap = device_node_to_regmap(mpmu);
> > > +		of_node_put(mpmu);
> > > +
> > you can simplify above with syscon_regmap_lookup_by_phandle(), which
> > would save a few lines
> > 
> > or further, just call syscon_regmap_lookup_by_compatible()? then
> > won't be necessary to introduce the "spacemit,mpmu" property..
> > 
> 
> These syscon_* functions differ a little from device_node_to_regmap():
> they get and enable the first item in "clocks" property when
> instantiating a regmap, which isn't desired for a clock controller.

Yes. And mpmu is not a syscon, so it would be inaccurate or even wrong
API to use.

Best regards,
Krzysztof


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
  2025-03-07  0:51   ` Yixun Lan
@ 2025-03-11 23:19   ` Alex Elder
  2025-03-20 22:39     ` Alex Elder
  2025-03-24 11:14     ` Haylen Chu
  2025-03-12 20:17   ` kernel test robot
  2025-03-18  5:37   ` Yixun Lan
  3 siblings, 2 replies; 26+ messages in thread
From: Alex Elder @ 2025-03-11 23:19 UTC (permalink / raw)
  To: Haylen Chu, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu

On 3/6/25 11:57 AM, Haylen Chu wrote:
> The clock tree of K1 SoC contains three main types of clock hardware
> (PLL/DDN/MIX) and has control registers split into several multifunction
> devices: APBS (PLLs), MPMU, APBC and APMU.
> 
> All register operations are done through regmap to ensure atomiciy
> between concurrent operations of clock driver and reset,
> power-domain driver that will be introduced in the future.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>

I'm very glad you have the DT issues resolved now.

I again have lots of comments on the code, and I think I've
identified a few bugs.  Most of my comments, however, are
suggesting minor changes for consistency and readability.

I'm going to skip over a lot of "ccu-k1.c" because most of what I
say applies to the definitions in the header files.

> ---
>   drivers/clk/Kconfig               |    1 +
>   drivers/clk/Makefile              |    1 +
>   drivers/clk/spacemit/Kconfig      |   20 +
>   drivers/clk/spacemit/Makefile     |    5 +
>   drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
>   drivers/clk/spacemit/ccu_common.h |   47 +
>   drivers/clk/spacemit/ccu_ddn.c    |   80 ++
>   drivers/clk/spacemit/ccu_ddn.h    |   48 +
>   drivers/clk/spacemit/ccu_mix.c    |  284 +++++
>   drivers/clk/spacemit/ccu_mix.h    |  246 +++++
>   drivers/clk/spacemit/ccu_pll.c    |  146 +++
>   drivers/clk/spacemit/ccu_pll.h    |   76 ++
>   12 files changed, 2668 insertions(+)
>   create mode 100644 drivers/clk/spacemit/Kconfig
>   create mode 100644 drivers/clk/spacemit/Makefile
>   create mode 100644 drivers/clk/spacemit/ccu-k1.c
>   create mode 100644 drivers/clk/spacemit/ccu_common.h
>   create mode 100644 drivers/clk/spacemit/ccu_ddn.c
>   create mode 100644 drivers/clk/spacemit/ccu_ddn.h
>   create mode 100644 drivers/clk/spacemit/ccu_mix.c
>   create mode 100644 drivers/clk/spacemit/ccu_mix.h
>   create mode 100644 drivers/clk/spacemit/ccu_pll.c
>   create mode 100644 drivers/clk/spacemit/ccu_pll.h
> 
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 713573b6c86c..19c1ed280fd7 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
>   source "drivers/clk/sifive/Kconfig"
>   source "drivers/clk/socfpga/Kconfig"
>   source "drivers/clk/sophgo/Kconfig"
> +source "drivers/clk/spacemit/Kconfig"
>   source "drivers/clk/sprd/Kconfig"
>   source "drivers/clk/starfive/Kconfig"
>   source "drivers/clk/sunxi/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index bf4bd45adc3a..42867cd37c33 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
>   obj-$(CONFIG_CLK_SIFIVE)		+= sifive/
>   obj-y					+= socfpga/
>   obj-y					+= sophgo/
> +obj-y					+= spacemit/
>   obj-$(CONFIG_PLAT_SPEAR)		+= spear/
>   obj-y					+= sprd/
>   obj-$(CONFIG_ARCH_STI)			+= st/
> diff --git a/drivers/clk/spacemit/Kconfig b/drivers/clk/spacemit/Kconfig
> new file mode 100644
> index 000000000000..76090cd85668
> --- /dev/null
> +++ b/drivers/clk/spacemit/Kconfig
> @@ -0,0 +1,20 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config SPACEMIT_CCU
> +	tristate "Clock support for Spacemit SoCs"
> +	default y
> +	depends on ARCH_SPACEMIT || COMPILE_TEST
> +	select MFD_SYSCON
> +	help
> +	  Say Y to enable clock controller unit support for Spacemit SoCs.
> +
> +if SPACEMIT_CCU
> +
> +config SPACEMIT_K1_CCU
> +	tristate "Support for Spacemit K1 SoC"
> +	default y
> +	depends on ARCH_SPACEMIT || COMPILE_TEST

It's great to include COMPILE_TEST.

> +	help
> +	  Support for clock controller unit in Spacemit K1 SoC.
> +
> +endif
> diff --git a/drivers/clk/spacemit/Makefile b/drivers/clk/spacemit/Makefile
> new file mode 100644
> index 000000000000..5ec6da61db98
> --- /dev/null
> +++ b/drivers/clk/spacemit/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_SPACEMIT_K1_CCU)	= spacemit-ccu-k1.o
> +spacemit-ccu-k1-y		= ccu_pll.o ccu_mix.o ccu_ddn.o
> +spacemit-ccu-k1-y		+= ccu-k1.o
> diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
> new file mode 100644
> index 000000000000..5974a0a1b5f6
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu-k1.c
> @@ -0,0 +1,1714 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/delay.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +
> +#include "ccu_common.h"
> +#include "ccu_pll.h"
> +#include "ccu_mix.h"
> +#include "ccu_ddn.h"
> +
> +#include <dt-bindings/clock/spacemit,k1-ccu.h>
> +
> +/*	APBS register offset	*/

Use spaces, not tabs above (same as you've done elsewhere).

> +#define APBS_PLL1_SWCR1			0x100
> +#define APBS_PLL1_SWCR2			0x104
> +#define APBS_PLL1_SWCR3			0x108
> +#define APBS_PLL2_SWCR1			0x118
> +#define APBS_PLL2_SWCR2			0x11c
> +#define APBS_PLL2_SWCR3			0x120
> +#define APBS_PLL3_SWCR1			0x124
> +#define APBS_PLL3_SWCR2			0x128
> +#define APBS_PLL3_SWCR3			0x12c
> +
> +/* MPMU register offset */
> +#define MPMU_POSR			0x10
> +#define POSR_PLL1_LOCK			BIT(27)
> +#define POSR_PLL2_LOCK			BIT(28)
> +#define POSR_PLL3_LOCK			BIT(29)
> +
> +#define MPMU_WDTPCR			0x200
> +#define MPMU_RIPCCR			0x210
> +#define MPMU_ACGR			0x1024
> +#define MPMU_SUCCR			0x14
> +#define MPMU_ISCCR			0x44

Somehow the fact that you used just two digits for the above
two made me think maybe they were field values rather than
offsets.

If you used 4 digits for all offsets (with leading 0's) maybe
that consistency would reinforce the difference between an
offset and other values.

Also, please sort the offset definitions in increasing
numeric order (within the groups); that too will help
readability.

> +#define MPMU_SUCCR_1			0x10b0
> +#define MPMU_APBCSCR			0x1050
> +
> +/* APBC register offset */
> +#define APBC_UART1_CLK_RST		0x0
> +#define APBC_UART2_CLK_RST		0x4
> +#define APBC_GPIO_CLK_RST		0x8

. . .

> +#define APBC_PWM17_CLK_RST		0xdc
> +#define APBC_PWM18_CLK_RST		0xe0
> +#define APBC_PWM19_CLK_RST		0xe4
> +

I realize that these symbols are named based on the
underlying hardware register names.  But the above
uses "CLK_RST" to indicate a clock/reset control
register.  But the definitions below spell "reset"
differently:  "CLK_RES".  What do you think about
using a consistent naming scheme in the code?

> +/* APMU register offset */
> +#define APMU_JPG_CLK_RES_CTRL		0x20
> +#define APMU_CSI_CCIC2_CLK_RES_CTRL	0x24
> +#define APMU_ISP_CLK_RES_CTRL		0x38
> +#define APMU_LCD_CLK_RES_CTRL1		0x44
> +#define APMU_LCD_SPI_CLK_RES_CTRL	0x48
> +#define APMU_LCD_CLK_RES_CTRL2		0x4c
> +#define APMU_CCIC_CLK_RES_CTRL		0x50
> +#define APMU_SDH0_CLK_RES_CTRL		0x54
> +#define APMU_SDH1_CLK_RES_CTRL		0x58
> +#define APMU_USB_CLK_RES_CTRL		0x5c
> +#define APMU_QSPI_CLK_RES_CTRL		0x60
> +#define APMU_DMA_CLK_RES_CTRL		0x64
> +#define APMU_AES_CLK_RES_CTRL		0x68
> +#define APMU_VPU_CLK_RES_CTRL		0xa4
> +#define APMU_GPU_CLK_RES_CTRL		0xcc
> +#define APMU_SDH2_CLK_RES_CTRL		0xe0
> +#define APMU_PMUA_MC_CTRL		0xe8
> +#define APMU_PMU_CC2_AP			0x100
> +#define APMU_PMUA_EM_CLK_RES_CTRL	0x104
> +#define APMU_AUDIO_CLK_RES_CTRL		0x14c
> +#define APMU_HDMI_CLK_RES_CTRL		0x1b8
> +#define APMU_CCI550_CLK_CTRL		0x300
> +#define APMU_ACLK_CLK_CTRL		0x388
> +#define APMU_CPU_C0_CLK_CTRL		0x38C
> +#define APMU_CPU_C1_CLK_CTRL		0x390
> +#define APMU_PCIE_CLK_RES_CTRL_0	0x3cc
> +#define APMU_PCIE_CLK_RES_CTRL_1	0x3d4
> +#define APMU_PCIE_CLK_RES_CTRL_2	0x3dc
> +#define APMU_EMAC0_CLK_RES_CTRL		0x3e4
> +#define APMU_EMAC1_CLK_RES_CTRL		0x3ec
> +
> +/*	APBS clocks start	*/
> +
> +/* Frequency of pll{1,2} should not be updated at runtime */

Given the clock tables below only define a single entry,
I don't believe it's even possible to update pll{1,2} at
runtime.  Maybe the above comment could go away.  If
you keep it, I'd say "must not" rather than "should not."

> +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = {
> +	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
> +};
> +
> +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = {
> +	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
> +};
> +
> +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = {
> +	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
> +	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
> +	CCU_PLL_RATE(3200000000UL, 0x0050dd67, 0x43eaaaab),
> +};
> +

I'm going to restate this later.  But I think you can include
"static" in the definition of your CCU_*_DEFINE() macros,
because all defined clocks will be static.  I believe they
can also get the const qualifier here.

> +static CCU_PLL_DEFINE(pll1, pll1_rate_tbl,
> +		      APBS_PLL1_SWCR1, APBS_PLL1_SWCR3,
> +		      MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE);
> +static CCU_PLL_DEFINE(pll2, pll2_rate_tbl,
> +		      APBS_PLL2_SWCR1, APBS_PLL2_SWCR3,
> +		      MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE);
> +static CCU_PLL_DEFINE(pll3, pll3_rate_tbl,
> +		      APBS_PLL3_SWCR1, APBS_PLL2_SWCR3,
> +		      MPMU_POSR, POSR_PLL3_LOCK, CLK_SET_RATE_GATE);
> +
> +static CCU_GATE_FACTOR_DEFINE(pll1_d2, CCU_PARENT_HW(pll1),
> +			      APBS_PLL1_SWCR2,
> +			      BIT(1), 2, 1, 0);

You seem to try to use multiple lines for these definitions
to possibly aid readability.  I think that's a good idea in
general.  But it isn't really consistent (I'll point out
a few examples below), and so it doesn't always work.  For
many (maybe all), the first line is consistent, but the rest
seem to vary a bit.

These definitions are basically hardware definitions.  So
assuming you get them all correct initially, they'll never
change (for a given platform, like K1), and readability is
less of an issue.

This time around I haven't scrutinized these in that much
detail.  But I think--once you're confident the definitions
are correct--you can simply format all of these clock
definitions to fit within 80 columns, and save a bunch of
lines in the source file.

> +static CCU_GATE_FACTOR_DEFINE(pll1_d3, CCU_PARENT_HW(pll1),
> +			      APBS_PLL1_SWCR2,
> +			      BIT(2), 3, 1, 0);

. . .

> +static CCU_GATE_FACTOR_DEFINE(pll3_d8, CCU_PARENT_HW(pll3),
> +			      APBS_PLL3_SWCR2,
> +			      BIT(7), 8, 1, 0);
> +

Here (below) you define these PLL "factor" clocks all on one line.

> +static CCU_FACTOR_DEFINE(pll3_20, CCU_PARENT_HW(pll3_d8), 20, 1);
> +static CCU_FACTOR_DEFINE(pll3_40, CCU_PARENT_HW(pll3_d8), 10, 1);
> +static CCU_FACTOR_DEFINE(pll3_80, CCU_PARENT_HW(pll3_d8), 5, 1);
> +
> +/*	APBS clocks end		*/

Use spaces rather than tabs, above and below (and throughout).
Or...  Use tabs consistently.  Pick one.

> +
> +/*	MPMU clocks start	*/
> +static CCU_GATE_DEFINE(pll1_d8_307p2, CCU_PARENT_HW(pll1_d8),
> +		       MPMU_ACGR,
> +		       BIT(13), 0);

But here you define the "factor" clock on two lines.  I'm sure
this is for line length.  I'd say just define them all according
to line length--make them fit within 80 columns, but otherwise
don't bother with extra lines for readability.

> +static CCU_FACTOR_DEFINE(pll1_d32_76p8, CCU_PARENT_HW(pll1_d8_307p2),
> +			 4, 1);

. . .

> +
> +static const struct clk_parent_data apb_parents[] = {
> +	CCU_PARENT_HW(pll1_d96_25p6),
> +	CCU_PARENT_HW(pll1_d48_51p2),
> +	CCU_PARENT_HW(pll1_d96_25p6),
> +	CCU_PARENT_HW(pll1_d24_102p4),
> +};

I think you should have a blank line after this definition (and
all other similar ones in this file).  In some cases you have
no blank line above this sort of definition, and you should.

> +static CCU_MUX_DEFINE(apb_clk, apb_parents,
> +		      MPMU_APBCSCR,
> +		      0, 2,
> +		      0);
> +
> +static CCU_GATE_DEFINE(wdt_bus_clk, CCU_PARENT_HW(apb_clk),
> +		       MPMU_WDTPCR,
> +		       BIT(0),
> +		       0);
> +
> +static CCU_GATE_DEFINE(ripc_clk, CCU_PARENT_HW(apb_clk),
> +		       MPMU_RIPCCR,
> +		       0x1,
> +		       0);
> +/*	MPMU clocks end		*/
> +
> +/*	APBC clocks start	*/
> +static const struct clk_parent_data uart_clk_parents[] = {
> +	CCU_PARENT_HW(pll1_m3d128_57p6),
> +	CCU_PARENT_HW(slow_uart1_14p74),
> +	CCU_PARENT_HW(slow_uart2_48),
> +};

Here you're putting the control register and flags field on
separate lines.  I'd say just join them.

> +static CCU_MUX_GATE_DEFINE(uart0_clk, uart_clk_parents,
> +			   APBC_UART1_CLK_RST,
> +			   4, 3, BIT(1),
> +			   CLK_IS_CRITICAL);

. . .

> +struct spacemit_ccu_clk {
> +	int id;
> +	struct clk_hw *hw;
> +};
> +

The following array defines the clocks under what is matched
as "spacemit,k1-pll".  I know it's the "apbs" (APB Spare)
memory region, but I think it would be better to have the
name here match the compatible string.  Add a comment if
you want to make sure people understand the "PLL<->APBS"
relationship.

> +static struct spacemit_ccu_clk k1_ccu_apbs_clks[] = {
> +	{ CLK_PLL1,		&pll1.common.hw },
> +	{ CLK_PLL2,		&pll2.common.hw },
> +	{ CLK_PLL3,		&pll3.common.hw },

. . .

> +static int spacemit_ccu_register(struct device *dev,
> +				 struct regmap *regmap, struct regmap *lock_regmap,
> +				 const struct spacemit_ccu_clk *clks)
> +{
> +	const struct spacemit_ccu_clk *clk;
> +	int i, ret, max_id = 0;
> +
> +	for (clk = clks; clk->hw; clk++)
> +		max_id = max(max_id, clk->id);
> +
> +	struct clk_hw_onecell_data *clk_data;

The definition of clk_data belongs at the top of the function.

> +
> +	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, max_id + 1), GFP_KERNEL);
> +	if (!clk_data)
> +		return -ENOMEM;
> +
> +	for (i = 0; i <= max_id; i++)
> +		clk_data->hws[i] = ERR_PTR(-ENOENT);
> +
> +	for (clk = clks; clk->hw; clk++) {
> +		struct ccu_common *common = hw_to_ccu_common(clk->hw);
> +		const char *name = clk->hw->init->name;
> +
> +		common->regmap		= regmap;
> +		common->lock_regmap	= lock_regmap;
> +
> +		ret = devm_clk_hw_register(dev, clk->hw);
> +		if (ret) {
> +			dev_err(dev, "Cannot register clock %d - %s\n",
> +				i, name);
> +			return ret;
> +		}
> +
> +		clk_data->hws[clk->id] = clk->hw;
> +	}
> +
> +	clk_data->num = max_id + 1;
> +
> +	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
> +}
> +
> +static int k1_ccu_probe(struct platform_device *pdev)
> +{
> +	struct regmap *base_regmap, *lock_regmap = NULL;
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	base_regmap = device_node_to_regmap(dev->of_node);
> +	if (IS_ERR(base_regmap))
> +		return dev_err_probe(dev, PTR_ERR(base_regmap),
> +				     "failed to get regmap\n");
> +

You should add a comment here explaining why there is a special
additional regmap for the PLL clock type (because it requires a
lock that's found in that that device node's memory region).

> +	if (of_device_is_compatible(dev->of_node, "spacemit,k1-pll")) {
> +		struct device_node *mpmu = of_parse_phandle(dev->of_node,
> +							    "spacemit,mpmu", 0);
> +		if (!mpmu)
> +			return dev_err_probe(dev, -ENODEV,
> +					     "Cannot parse MPMU region\n");
> +
> +		lock_regmap = device_node_to_regmap(mpmu);
> +		of_node_put(mpmu);
> +
> +		if (IS_ERR(lock_regmap))
> +			return dev_err_probe(dev, PTR_ERR(lock_regmap),
> +					     "failed to get lock regmap\n");
> +	}
> +
> +	ret = spacemit_ccu_register(dev, base_regmap, lock_regmap,
> +				    of_device_get_match_data(dev));
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to register clocks\n");
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id of_k1_ccu_match[] = {
> +	{
> +		.compatible	= "spacemit,k1-pll",
> +		.data		= k1_ccu_apbs_clks,
> +	},
> +	{
> +		.compatible	= "spacemit,k1-syscon-mpmu",
> +		.data		= k1_ccu_mpmu_clks,
> +	},
> +	{
> +		.compatible	= "spacemit,k1-syscon-apbc",
> +		.data		= k1_ccu_apbc_clks,
> +	},
> +	{
> +		.compatible	= "spacemit,k1-syscon-apmu",
> +		.data		= k1_ccu_apmu_clks,
> +	},
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, of_k1_ccu_match);
> +
> +static struct platform_driver k1_ccu_driver = {
> +	.driver = {
> +		.name		= "spacemit,k1-ccu",
> +		.of_match_table = of_k1_ccu_match,
> +	},
> +	.probe	= k1_ccu_probe,
> +};
> +module_platform_driver(k1_ccu_driver);
> +
> +MODULE_DESCRIPTION("Spacemit K1 CCU driver");
> +MODULE_AUTHOR("Haylen Chu <heylenay@4d2.org>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
> new file mode 100644
> index 000000000000..494cde96fe3c
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_common.h
> @@ -0,0 +1,47 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_COMMON_H_
> +#define _CCU_COMMON_H_
> +
> +#include <linux/regmap.h>
> +

I'm not going to suggest it at this point, but it might
have worked out more nicely if you defined a top-level CCU
structure that contained a union of structs, one for each
type of clock (PLL, DDN, mix).

> +struct ccu_common {
> +	struct regmap *regmap;
> +	struct regmap *lock_regmap;

The lock_regmap is only used for PLL type clocks, right?
So it could be included in the PLL struct within the union
below?

> +
> +	union {
> +		/* For DDN and MIX */
> +		struct {
> +			u32 reg_ctrl;
> +			u32 reg_fc;
> +			u32 fc;

The fc field is a bit mask, and with a single bit set.

Other fields you define use the convention "mask" in the name
to indicate it is a bit mask.  So I suggest you name "fc" to
be "mask_fc" or "fc_mask" (but in the latter case, I'd rename
the registers to be "fc_reg" and "ctrl_reg"--you decide).

Since fc is a (nonzero) mask, you could use a zero fc value
to indicate that reg_fc is the same as reg_ctrl.

> +		};
> +
> +		/* For PLL */
> +		struct {
> +			u32 reg_swcr1;
> +			u32 reg_swcr2;

The reg_swcr2 is no longer used (now that you've dropped it
from the ccu_pll_rate_tbl structure).

> +			u32 reg_swcr3;

You define PLL_SWCR3_EN in "ccu_pll.c" to have value BIT(31).
that's good.  But you should define its inverse, to define
which bits in the reg_swcr3 field are the valid "magic" part.
In both cases, I would define them here in this file, where
the structure type is defined (not in "ccu_pll.c").

#define SPACEMIT_PLL_SWCR3_EN	(u32)BIT(31)
#define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)

> +		};
> +	};
> +
> +	struct clk_hw hw;
> +};
> +
> +static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
> +{
> +	return container_of(hw, struct ccu_common, hw);
> +}
> +

This isn't a huge deal, but I think since your functions here
are named ccu_*(), the first argument should be the CCU (or
ccu_common) structure.

> +#define ccu_read(reg, c, val)	regmap_read((c)->regmap, (c)->reg_##reg, val)

I commented before that you're not checking return values for
regmap_read() and regmap_write().  You said you don't expect
MMIO accesses to ever fail.

If we assume that, I'd like to see ccu_read() defined differently,
so that it returns a value (rather than taking the assigned-to
address as an argument).  To me, it makes it much easier to
understand in places it's used, making it more obvious an
assignment is getting made.

static inline u32 _ccu_read(struct ccu_common *common, u32 offset)
{
	u32 val;

	(void)regmap_read(common->regmap, offset>reg_##reg, &val);

	return val;
}
#define ccu_read(reg, c)	_ccu_read((c), (c)->reg_##reg)

	val = ccu_read(ctrl, &ddn->common);


> +#define ccu_update(reg, c, mask, val) \
> +	regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)7
> +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \
> +	regmap_read_poll_timeout_atomic((c)->regmap, (c)->reg_##reg,	\
> +					tmp, cond, sleep, timeout)
> +
> +#endif /* _CCU_COMMON_H_ */
> diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c
> new file mode 100644
> index 000000000000..ee187687d0c4
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_ddn.c
> @@ -0,0 +1,80 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit clock type ddn
> + *
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/rational.h>
> +
> +#include "ccu_ddn.h"
> +
> +/*
> + * DDN stands for "Divider Denominator Numerator", it's M/N clock with a
> + * constant x2 factor. This clock hardware follows the equation below,
> + *
> + *	      numerator       Fin
> + *	2 * ------------- = -------
> + *	     denominator      Fout
> + *
> + * Thus, Fout could be calculated with,
> + *
> + *		Fin	denominator
> + *	Fout = ----- * -------------
> + *		 2	 numerator

Thank you for this clear explanation.  I still think the terminology
is weird but with the above it's at least understandable.

> + */
> +
> +static unsigned long clk_ddn_calc_best_rate(struct ccu_ddn *ddn,
> +					    unsigned long rate, unsigned long prate,
> +					    unsigned long *num, unsigned long *den)
> +{
> +	rational_best_approximation(rate, prate / 2,
> +				    ddn->den_mask, ddn->num_mask,
> +				    den, num);

Using rational_best_approximation() is excellent.  However I
think you have a bug, and I don't think the exact way you're
using it is clear (and might be wrong).

The bug is that the third and fourth arguments are the maximum
numerator and denominator, respectively.  You are passing mask
values, which in some sense represent the maximums.  However,
your masks are not always in the low-order bits.  Here is one
example:

static CCU_DDN_DEFINE(slow_uart1_14p74, pll1_d16_153p6,
                       MPMU_SUCCR,
                       GENMASK(28, 16), 16, GENMASK(12, 0), 0,
                       0);

The "_num_mask" argument to this macro is 0x1fff0000, and the
"_den_mask" is 0x00000fff.  The latter value (which gets passed
as the max_numerator argument to rational_best_approximation())
is fine, but the former is not.  So you need to shift both masks
right by their corresponding shift value.

Beyond that bug, rational_best_approximation() wants its first
two arguments to define the desired rate (as a fraction).  So
the desired rate should be the actual desired rate divided by 1
(rather than being divided by the half the parent rate).  So
this too might be a bug.

Maybe I'm misinterpreting this, but the bottom line is I'm
pretty certain this is not producing the correct numerator
and denominator that would produce the closest rate supported
by the hardware.

> +	return prate / 2 * *den / *num;

The above matches your explanation of how the output rate
is computed based on the "denominator" and "numerator".
You could encapsulate simply that in a function, given
tnat you used it here and another time below.

You might get slightly better precision by deferring the
divide-by-2.


> +}
> +
> +static long clk_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long *prate)
> +{
> +	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
> +	unsigned long num = 0, den = 0;

There should be no need to initialize num and den to 0.

> +
> +	return clk_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
> +}
> +
> +static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
> +{
> +	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
> +	unsigned int val, num, den;
> +
> +	ccu_read(ctrl, &ddn->common, &val);
> +
> +	num = (val & ddn->num_mask) >> ddn->num_shift;
> +	den = (val & ddn->den_mask) >> ddn->den_shift;

I have a strong preference to use a single mask value rather
than a shift and width (or in this case, shift and mask).
But  I know FIELD_GET() requires a constant mask value.

I'll add a comment below (in "ccu_ddn.h") that might address
my concerns a little bit in this case.

> +
> +	return prate / 2 * den / num;
> +}
> +
> +static int clk_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long prate)
> +{
> +	struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
> +	unsigned long num, den;
> +
> +	clk_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
> +
> +	ccu_update(ctrl, &ddn->common,
> +		   ddn->num_mask | ddn->den_mask,
> +		   (num << ddn->num_shift) | (den << ddn->den_shift));
> +
> +	return 0;
> +}
> +
> +const struct clk_ops spacemit_ccu_ddn_ops = {
> +	.recalc_rate	= clk_ddn_recalc_rate,
> +	.round_rate	= clk_ddn_round_rate,
> +	.set_rate	= clk_ddn_set_rate,
> +};
> diff --git a/drivers/clk/spacemit/ccu_ddn.h b/drivers/clk/spacemit/ccu_ddn.h
> new file mode 100644
> index 000000000000..3746d084e1e7
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_ddn.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_DDN_H_
> +#define _CCU_DDN_H_
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_common.h"
> +
> +struct ccu_ddn {
> +	struct ccu_common common;
> +	unsigned int num_mask;
> +	unsigned int num_shift;
> +	unsigned int den_mask;
> +	unsigned int den_shift;
> +};
> +
> +#define CCU_DDN_INIT(_name, _parent, _flags) \
> +	CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
> +

In CCU_DDN_DEFINE(), both the numerator and denominator masks
consist of a contiguous block of 1 bits, with zero or more 0
bits to their left and right.  They are always constant (both
times it's used...).

For that reason, I'd like you to drop the _num_shift and _den_shift
arguments from this macro.  Instead, use __ffs(_num_mask) and
__ffs(_den_mask) when assigning the num_shift and den_shift field
values.

> +#define CCU_DDN_DEFINE(_name, _parent, _reg_ctrl,				\
> +		       _num_mask, _num_shift, _den_mask, _den_shift,		\
> +		       _flags)							\

As I mentioned earlier, I'm sure that every one of these defined
clocks should be both private (static) and constant (const).  I
recommend you include both of those keywords in these CCU_*_DEFINE()
macro definitions.

static const struct ccu_ddn _name = { \

Also, in the definitions of these CCU_*_DEFINE() macros later, you
align the struct below at the left column.  Do that here too.

> +	struct ccu_ddn _name = {						\
> +		.common = {							\
> +			.reg_ctrl = _reg_ctrl,					\
> +			.hw.init  = CCU_DDN_INIT(_name, _parent, _flags),	\
> +		},								\
> +		.num_mask = _num_mask,						\
> +		.num_shift = _num_shift,					\
> +		.den_mask = _den_mask,						\
> +		.den_shift = _den_shift,					\
> +	}
> +
> +static inline struct ccu_ddn *hw_to_ccu_ddn(struct clk_hw *hw)
> +{
> +	struct ccu_common *common = hw_to_ccu_common(hw);
> +
> +	return container_of(common, struct ccu_ddn, common);
> +}
> +
> +extern const struct clk_ops spacemit_ccu_ddn_ops;
> +
> +#endif
> diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c
> new file mode 100644
> index 000000000000..a5c13000e062
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_mix.c
> @@ -0,0 +1,284 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit clock type mix(div/mux/gate/factor)
> + *
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_mix.h"
> +

I think the name of this constant should include "FC", and a
comment should indicate it is a number of microseconds.  "FC"
stands for "frequency change"; I think you should have a comment
explaining exactly what that means (somewhere).

> +#define MIX_TIMEOUT	10000
> +
> +static void ccu_gate_disable(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +
> +	ccu_update(ctrl, common, mix->gate.mask, 0);
> +}

Maybe drop the second local variable and do:

	ccu_update(ctrl, &mix->common, mix->gate.mask, 0);

And this can be done several times below.

> +
> +static int ccu_gate_enable(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_gate_config *gate = &mix->gate;
> +
> +	ccu_update(ctrl, common, gate->mask, gate->mask);
> +
> +	return 0;
> +}
> +
> +static int ccu_gate_is_enabled(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	u32 tmp;
> +
> +	ccu_read(ctrl, common, &tmp);
> +

The next line won't necessarily work.  The gate mask
value in a few cases contains more than one set bit
(rtc_clk is an exmaple).  Therefore this will return
true even if just one of those bits is set.  This is
most likely a bug.

This needs to be:

	return (tmp & mix->gete.mask) == mix->gate.mask;

> +	return !!(tmp & mix->gate.mask);
> +}
> +
> +static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
> +					    unsigned long parent_rate)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +
> +	return parent_rate * mix->factor.mul / mix->factor.div;
> +}
> +
> +static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_div_config *div = &mix->div;
> +	unsigned long val;
> +	u32 reg;
> +
> +	ccu_read(ctrl, common, &reg);
> +
> +	val = reg >> div->shift;
> +	val &= (1 << div->width) - 1;
> +
> +	val = divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
> +
> +	return val;
> +}
> +
> +static int ccu_mix_trigger_fc(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;

You don't use the mix pointer, so you can just do:

	struct ccu_common *common = hw_to_ccu_common(hw);

> +	unsigned int val = 0;
> +

I'm pretty sure you don't need to initialze val to 0, given
you're assuming regmap_read() won't return errors.

Have this function return 0 immediately if there is no
frequency control register/mask:

	if (!common->fc_mask)
		return 0;

This will simplify the callers.
		
> +	ccu_update(fc, common, common->fc, common->fc);
> +
> +	return ccu_poll(fc, common, val, !(val & common->fc),
> +			5, MIX_TIMEOUT);

You should add this above, where you define MIX_TIMEOUT, and
use it here.

#define CCU_FC_MIX_DELAY	5	/* microseconds */

> +}
> +
> +static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate,
> +				  unsigned long *prate)
> +{
> +	return ccu_factor_recalc_rate(hw, *prate);
> +}
> +
> +static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long parent_rate)
> +{
> +	return 0;
> +}
> +
> +static unsigned long
> +ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
> +		       struct clk_hw **best_parent,
> +		       unsigned long *best_parent_rate,
> +		       u32 *div_val)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	unsigned int parent_num = clk_hw_get_num_parents(hw);
> +	struct ccu_div_config *div = &mix->div;
> +	u32 div_max = 1 << div->width;
> +	unsigned long best_rate = 0;
> +
> +	for (int i = 0; i < parent_num; i++) {
> +		struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
> +		unsigned long parent_rate;
> +
> +		if (!parent)
> +			continue;
> +
> +		parent_rate = clk_hw_get_rate(parent);
> +
> +		for (int j = 1; j <= div_max; j++) {
> +			unsigned long tmp = DIV_ROUND_UP_ULL(parent_rate, j);

I might have asked this before.  Why round up?  Why not
round closest?

> +
> +			if (abs(tmp - rate) < abs(best_rate - rate)) {
> +				best_rate = tmp;
> +
> +				if (div_val)
> +					*div_val = j - 1;
> +
> +				if (best_parent) {
> +					*best_parent      = parent;
> +					*best_parent_rate = parent_rate;
> +				}
> +			}
> +		}
> +	}
> +
> +	return best_rate;
> +}
> +
> +static int ccu_mix_determine_rate(struct clk_hw *hw,
> +				  struct clk_rate_request *req)
> +{
> +	req->rate = ccu_mix_calc_best_rate(hw, req->rate,
> +					   &req->best_parent_hw,
> +					   &req->best_parent_rate,
> +					   NULL);
> +	return 0;
> +}
> +
> +static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_div_config *div = &mix->div;
> +	int ret = 0, tmp = 0;

No need to initialize ret or tmp.

> +	u32 current_div, target_div;
> +
> +	ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
> +
> +	ccu_read(ctrl, common, &tmp);
> +
> +	current_div = tmp >> div->shift;
> +	current_div &= (1 << div->width) - 1;
> +
> +	if (current_div == target_div)
> +		return 0;
> +
> +	tmp = GENMASK(div->width + div->shift - 1, div->shift);
> +
> +	ccu_update(ctrl, common, tmp, target_div << div->shift);
> +
> +	if (common->reg_fc)
> +		ret = ccu_mix_trigger_fc(hw);

Drop the test for reg_fc here, and hide those details
inside the called function.  Then:

	return ccu_mix_trigger_fc(hw);

> +
> +	return ret;
> +}
> +
> +static u8 ccu_mux_get_parent(struct clk_hw *hw)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_mux_config *mux = &mix->mux;
> +	u32 reg;
> +	u8 parent;
> +
> +	ccu_read(ctrl, common, &reg);
> +
> +	parent = reg >> mux->shift;
> +	parent &= (1 << mux->width) - 1;
> +
> +	return parent;
> +}
> +
> +static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> +	struct ccu_common *common = &mix->common;
> +	struct ccu_mux_config *mux = &mix->mux;
> +	int ret = 0;
> +	u32 mask;
> +

I'm not sure it will ever happen, but if index is out of range
this most likely won't do the right thing.

	if (WARN_ON(index >= 1 << mux->width))
		return -EINVAL;

> +	mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
> +
> +	ccu_update(ctrl, common, mask, index << mux->shift);
> +
> +	if (common->reg_fc)
> +		ret = ccu_mix_trigger_fc(hw);
> +
> +	return ret;
> +}
> +

I think your naming for these should follow a pattern, for
example:
   gate then (factor or div) then mux then fc


> +const struct clk_ops spacemit_ccu_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +};
> +
> +const struct clk_ops spacemit_ccu_factor_ops = {
> +	.round_rate	= ccu_factor_round_rate,
> +	.recalc_rate	= ccu_factor_recalc_rate,
> +	.set_rate	= ccu_factor_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_mux_ops = {
> +	.determine_rate = ccu_mix_determine_rate,
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_ops = {
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_gate_factor_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.round_rate	= ccu_factor_round_rate,
> +	.recalc_rate	= ccu_factor_recalc_rate,
> +	.set_rate	= ccu_factor_set_rate,
> +};
> +

If you follow the pattern I said above, this would be
spacemit_ccu_gate_mux_ops().

(And so on.)

> +const struct clk_ops spacemit_ccu_mux_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_mux_gate_ops = {
> +	.disable	= ccu_gate_disable,
> +	.enable		= ccu_gate_enable,
> +	.is_enabled	= ccu_gate_is_enabled,
> +
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> +
> +const struct clk_ops spacemit_ccu_div_mux_ops = {
> +	.get_parent	= ccu_mux_get_parent,
> +	.set_parent	= ccu_mux_set_parent,
> +
> +	.determine_rate = ccu_mix_determine_rate,
> +	.recalc_rate	= ccu_div_recalc_rate,
> +	.set_rate	= ccu_mix_set_rate,
> +};
> diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h
> new file mode 100644
> index 000000000000..a3aa292d073d
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_mix.h
> @@ -0,0 +1,246 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_MIX_H_
> +#define _CCU_MIX_H_
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_common.h"
> +
> +struct ccu_gate_config {
> +	u32 mask;

Add a comment indicating this mask can contain more than just
one set bit.

> +};
> +
> +struct ccu_factor_config {
> +	u32 div;
> +	u32 mul;
> +};
> +
> +struct ccu_mux_config {
> +	u8 shift;
> +	u8 width;

There's a small chance that these being defined with u8 will
require them to be promoted to u32 where used.  I think the
u8 is just fine, but you might need to be careful.

> +};
> +
> +struct ccu_div_config {
> +	u8 shift;
> +	u8 width;
> +};
> +
> +struct ccu_mix {
> +	struct ccu_factor_config factor;
> +	struct ccu_gate_config gate;
> +	struct ccu_div_config div;
> +	struct ccu_mux_config mux;
> +	struct ccu_common common;
> +};
> +

I suggest aligning the "{" in the next few lines.

> +#define CCU_GATE_INIT(_mask) { .mask = _mask }
> +#define CCU_FACTOR_INIT(_div, _mul) { .div = _div, .mul = _mul }
> +#define CCU_MUX_INIT(_shift, _width) { .shift = _shift, .width = _width }
> +#define CCU_DIV_INIT(_shift, _width) { .shift = _shift, .width = _width }
> +

I think you should define these next two with their types.

> +#define CCU_PARENT_HW(_parent)		{ .hw = &_parent.common.hw }
> +#define CCU_PARENT_NAME(_name)		{ .fw_name = #_name }
> +
#define CCU_PARENT_HW(_parent) \
	(struct clk_parent_data){ .hw = &_parent.common.hw }

> +#define CCU_MIX_INITHW(_name, _parent, _ops, _flags)			\
> +	(&(struct clk_init_data) {					\

I'm pretty sure the left parenthesis above and its matching
right parenthesis below are not needed.

> +		.flags		= _flags,				\
> +		.name		= #_name,				\

If the type is defined above, you don't need to cast it here.
Also the curly braces around _parent look strange to me.  The
parent_data field is a pointer, and ideally there should be
no need to use a type cast here.

> +		.parent_data	= (const struct clk_parent_data[])	\
> +					{ _parent },			\
> +		.num_parents	= 1,					\
> +		.ops		= &_ops,				\
> +	})
> +
> +#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags)		\
> +	CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
> +

Many/most of the other CCU_*_DEFINE() macros use the name
_ctrl_reg for the register.  I suggest you use that here
(and everywhere, for consistency).

> +#define CCU_GATE_DEFINE(_name, _parent, _reg, _gate_mask, _flags)		\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.common	= {								\
> +		.reg_ctrl	= _reg,						\

Why do you have a tab before the equal sign here?  (But not
elsewhere?)  Whatever you decide, be consistent.

> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_gate_ops, _flags),	\
> +	}									\
> +}
> +
> +#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul)				\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
> +	.common = {								\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_factor_ops, 0),		\
> +	}									\
> +}
> +
> +#define CCU_MUX_DEFINE(_name, _parents, _reg, _shift, _width, _flags)		\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.mux	= CCU_MUX_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_mux_ops,	_flags),\
> +	}									\
> +}
> +
> +#define CCU_DIV_DEFINE(_name, _parent, _reg, _shift, _width, _flags)		\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.div	= CCU_DIV_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_div_ops, _flags)		\
> +	}									\
> +}
> +
> +#define CCU_GATE_FACTOR_DEFINE(_name, _parent,					\
> +			       _reg,						\
> +			       _gate_mask,					\
> +			       _div, _mul,					\
> +			       _flags)						\

static const struct ccu_mix _name = { \

I believe the value of _flags is always zero, so that argument
could be eliminated (it can be added back in the future if it's
ever needed).

There are only two cases where the value of _mul is different
from 1.  You could define a slightly different macro for those
cases, and for this, remove the multiplier argument.

> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_gate_factor_ops, _flags)	\
> +	}									\
> +}
> +
> +#define CCU_MUX_GATE_DEFINE(_name, _parents,					\
> +			    _reg,						\
> +			    _shift, _width,					\
> +			    _gate_mask,						\
> +			    _flags)						\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.mux	= CCU_MUX_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_mux_gate_ops,	\
> +						  _flags),			\
> +	}									\
> +}
> +
> +#define CCU_DIV_GATE_DEFINE(_name, _parent,					\
> +			    _reg,						\
> +			    _shift, _width,					\
> +			    _gate_mask,						\
> +			    _flags)						\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.div	= CCU_DIV_INIT(_shift, _width),					\
> +	.common = {								\
> +		.reg_ctrl	= _reg,						\
> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> +					  spacemit_ccu_div_gate_ops, _flags),	\
> +	}									\
> +}
> +
> +#define CCU_DIV_MUX_GATE_DEFINE(_name, _parents,				\
> +				_reg_ctrl,					\
> +				_mshift, _mwidth, _muxshift, _muxwidth,		\
> +				_gate_mask,					\
> +				_flags)						\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common	= {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_div_mux_gate_ops,\
> +						  _flags),			\
> +	},									\
> +}
> +

I think I might have said this elsewhere;  use _fc_mask
(or something similar) to define what you call _fc here.

> +#define CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents,			\
> +					 _reg_ctrl, _reg_fc,			\
> +					 _mshift, _mwidth,			\
> +					 _fc,					\
> +					 _muxshift, _muxwidth,			\
> +					 _gate_mask,				\
> +					 _flags)				\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common = {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.reg_fc		= _reg_fc,					\
> +		.fc		= _fc,						\

In all cases, these FC masks have a single bit set; this is
something I think is worth mentioning in a comment.

> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_div_mux_gate_ops,\
> +						  _flags),			\
> +	},									\
> +}
> +
> +#define CCU_DIV_FC_MUX_GATE_DEFINE(_name, _parents,				\
> +				   _reg_ctrl,					\
> +				   _mshift, _mwidth,				\
> +				   _fc,						\
> +				   _muxshift, _muxwidth,			\
> +				   _gate_mask, _flags)		\
> +CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents, _reg_ctrl, _reg_ctrl,		\
> +				 _mshift, _mwidth, _fc, _muxshift, _muxwidth,		\
> +				 _gate_mask, _flags)
> +
> +#define CCU_DIV_FC_MUX_DEFINE(_name, _parents,					\
> +			      _reg_ctrl,					\
> +			      _mshift, _mwidth,					\
> +			      _fc,						\
> +			      _muxshift, _muxwidth,				\
> +			      _flags)						\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common = {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.reg_fc		= _reg_ctrl,					\

I suggested elsewhere that you could use a non-zero FC mask
to indicate the FC register is the same as the reg_ctrl
register.  (Despite my repeated suggestion, what you're
doing already is fine...)

> +		.fc		= _fc,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_div_mux_ops,	\
> +						  _flags),			\
> +	},									\
> +}
> +
> +#define CCU_MUX_FC_DEFINE(_name, _parents,					\
> +			  _reg_ctrl,						\
> +			  _fc,							\
> +			  _muxshift, _muxwidth,					\
> +			  _flags)						\

static const struct ccu_mix _name = { \

> +struct ccu_mix _name = {							\
> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> +	.common = {								\
> +		.reg_ctrl	= _reg_ctrl,					\
> +		.reg_fc		= _reg_ctrl,					\
> +		.fc		= _fc,						\
> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> +						  spacemit_ccu_mux_ops,	_flags)	\
> +	},									\
> +}
> +
> +static inline struct ccu_mix *hw_to_ccu_mix(struct clk_hw *hw)
> +{
> +	struct ccu_common *common = hw_to_ccu_common(hw);
> +
> +	return container_of(common, struct ccu_mix, common);
> +}
> +

I think you can define each ops variable on its own line.

> +extern const struct clk_ops spacemit_ccu_gate_ops, spacemit_ccu_factor_ops;
> +extern const struct clk_ops spacemit_ccu_mux_ops, spacemit_ccu_div_ops;
> +
> +extern const struct clk_ops spacemit_ccu_gate_factor_ops;
> +extern const struct clk_ops spacemit_ccu_div_gate_ops;
> +extern const struct clk_ops spacemit_ccu_mux_gate_ops;
> +extern const struct clk_ops spacemit_ccu_div_mux_ops;
> +
> +extern const struct clk_ops spacemit_ccu_div_mux_gate_ops;
> +#endif /* _CCU_DIV_H_ */
> diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
> new file mode 100644
> index 000000000000..9df2149f6c98
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_pll.c
> @@ -0,0 +1,146 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Spacemit clock type pll
> + *
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/regmap.h>
> +
> +#include "ccu_common.h"
> +#include "ccu_pll.h"
> +

Name this just PLL_TIMEOUT, or maybe CCU_PLL_TIMEOUT, and
indicate in a comment that it is a period in microseconds.

> +#define PLL_DELAY_TIME	3000

You should also define this, as done earlier when polling
for the frequency change:

#define PLL_DELAY		5	/* microseconds */

> +#define PLL_SWCR3_EN	BIT(31)

Define this with the type; otherwise it's possible to get
warnings when you bitwise invert it.

#define PLL_SWCR3_EN	(u32)BIT(31)

> +
> +static int ccu_pll_is_enabled(struct clk_hw *hw)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);

	struct ccu_common *common = hw_to_ccu_common(hw);

> +	u32 tmp;
> +
> +	ccu_read(swcr3, &p->common, &tmp);
> +
> +	return tmp & PLL_SWCR3_EN;

	return !!(tmp & PLL_SWCR3_EN);
> +}
> +
> +/* frequency unit Mhz, return pll vco freq */
> +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
> +{
> +	const struct ccu_pll_rate_tbl *pll_rate_table;
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +	u32 swcr1, swcr3, size;
> +	int i;
> +
> +	ccu_read(swcr1, common, &swcr1);
> +	ccu_read(swcr3, common, &swcr3);

You are masking off the EN bit, but you should really be
using a mask defining which bits are valid instead.  As
I said earlier:

#define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)

> +	swcr3 &= ~PLL_SWCR3_EN;

	swcr3 &= SPACEMIT_PLL_SWCR3_MASK;
> +
> +	pll_rate_table = p->pll.rate_tbl;
> +	size = p->pll.tbl_size;
> +
> +	for (i = 0; i < size; i++) {
> +		if (pll_rate_table[i].swcr1 == swcr1 &&
> +		    pll_rate_table[i].swcr3 == swcr3)
> +			return pll_rate_table[i].rate;
> +	}
> +

I have a general question here.  Once you set one of these
clock rates, it will always use one of the rates defined
in the table.

But what about initially?  Could the hardware start in a
state that is not defined by this code?  Do you *set* the
rate initially?  Should you (at least the first time the
clock is prepared/enabled)?

> +	WARN_ON_ONCE(1);

Maybe WARN_ONCE(true, "msg");

> +
> +	return 0;
> +}
> +
> +static int ccu_pll_enable(struct clk_hw *hw)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +	unsigned int tmp;
> +	int ret;
> +

Get rid of ret (see below).

Will clk_ops->enable() ever be called when it's already
enabled?  (If it won't, this isn't needed.  If it will,
this checks the hardware, which is good.)

> +	if (ccu_pll_is_enabled(hw))
> +		return 0;
> +
> +	ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN);
> +
> +	/* check lock status */
> +	ret = regmap_read_poll_timeout_atomic(common->lock_regmap,
> +					      p->pll.reg_lock,
> +					      tmp,
> +					      tmp & p->pll.lock_enable_bit,
> +					      5, PLL_DELAY_TIME);

Just:

	return regmap_read_poll_timeout_atomic(...);

I note that you call this here, but you hide the call
to regmap_read_poll_timeout_atomic() behind the macro
ccu_poll().  And ccu_poll() (used for the FC bit) is
also only called once.

I suggest you get rid of regmap_poll() and just open-code
it.

(You use ccu_read() and ccu_update() numerous times, so
your "saving some characters" is justified.)

> +
> +	return ret;
> +}
> +
> +static void ccu_pll_disable(struct clk_hw *hw)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;

	struct ccu_common *common = hw_to_ccu_common(hw);

> +
> +	ccu_update(swcr3, common, PLL_SWCR3_EN, 0);
> +}
> +
> +/*
> + * PLLs must be gated before changing rate, which is ensured by
> + * flag CLK_SET_RATE_GATE.
> + */
> +static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> +			    unsigned long parent_rate)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_common *common = &p->common;
> +	struct ccu_pll_config *params = &p->pll;
> +	const struct ccu_pll_rate_tbl *entry = NULL;
> +	int i;
> +
> +	for (i = 0; i < params->tbl_size; i++) {
> +		if (rate == params->rate_tbl[i].rate) {
> +			entry = &params->rate_tbl[i];
> +			break;
> +		}
> +	}
> +
> +	if (WARN_ON_ONCE(!entry))
> +		return -EINVAL;
> +

The next line contains a bug.  The third argument defines
a mask of which bits get updated.  Therefore, any zero bits
in that mask will *not* get updated in the swcr1 register
even if they should be.

Example:
Old SWCR1:	0x01234567
New SWCR1:	0x00112233

Updated:	0x01336777
Want:		0x00112233

You should either define ccu_write() (my preference), or
you you should call:

	ccu_update(swcr1, common, ~0, entry->swcr1);


> +	ccu_update(swcr1, common, entry->swcr1, entry->swcr1);

You should use SPACEMIT_PLL_SWCR3_MASK below.  No
cast should be needed (define the symbol with one).

> +	ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3);
> +
> +	return 0;
> +}
> +
> +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
> +					 unsigned long parent_rate)
> +{
> +	return ccu_pll_get_vco_freq(hw);
> +}
> +
> +static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +			       unsigned long *prate)
> +{
> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> +	struct ccu_pll_config *params = &p->pll;
> +	unsigned int i;
> +

I'm pretty sure I mentioned this before.  If you cant find a
matching rate, you return the closest rate *less than* what
is requested, which might not be as close as the closest
rate *greater than* the requested rate.

In ccu_mix_calc_best_rate() you actually take this into account
(though you use DIV_ROUND_UP_ULL() there too).

> +	for (i = 0; i < params->tbl_size; i++) {
> +		if (params->rate_tbl[i].rate > rate) {
> +			i--;
> +			break;
> +		}
> +	}
> +
> +	return rate;
> +}
> +
> +const struct clk_ops spacemit_ccu_pll_ops = {
> +	.enable		= ccu_pll_enable,
> +	.disable	= ccu_pll_disable,
> +	.set_rate	= ccu_pll_set_rate,
> +	.recalc_rate	= ccu_pll_recalc_rate,
> +	.round_rate	= ccu_pll_round_rate,
> +	.is_enabled	= ccu_pll_is_enabled,
> +};
> +
> diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h
> new file mode 100644
> index 000000000000..c6a3a5cce995
> --- /dev/null
> +++ b/drivers/clk/spacemit/ccu_pll.h
> @@ -0,0 +1,76 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> + */
> +
> +#ifndef _CCU_PLL_H_
> +#define _CCU_PLL_H_
> +
> +#include <linux/clk-provider.h>
> +
> +#include "ccu_common.h"
> +

You should add a comment above this structure definition explaining
how exactly it's used.  Namely that SWCR1 and SWCR3 contain encoded
values, and each particular pair of these values creates an output
clock rate that's indicated.  Also note that while all bits in SWCR1
are valid, only the bottom 31 bits of SWCR3 are (its top bit is
used for locking).

> +struct ccu_pll_rate_tbl {
> +	unsigned long rate;
> +	u32 swcr1;
> +	u32 swcr3;
> +};
> +
> +struct ccu_pll_config {
> +	const struct ccu_pll_rate_tbl *rate_tbl;
> +	u32 tbl_size;

I dislike "size" as a name for something that is a "count".
To me, "size" is a number of bytes.

You (and clock code) use "num" elsewhere, but either way,
please use something different from "size" here.

> +	u32 reg_lock;
> +	u32 lock_enable_bit;

Use "lock_mask" here, hopefully matching a pattern you use
elsewhere that you define a register and a bit mask.  These
masks (all three places this is used) all have just one bit set.

> +};
> +
> +#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
> +	{									\
> +		.rate	= _rate,							\
> +		.swcr1	= _swcr1,						\
> +		.swcr3	= _swcr3,						\
> +	}
> +
> +struct ccu_pll {
> +	struct ccu_pll_config	pll;
> +	struct ccu_common	common;
> +};
> +
> +#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \
> +	{									\
> +		.rate_tbl	 = _table,					\
> +		.tbl_size	 = ARRAY_SIZE(_table),				\
> +		.reg_lock	 = (_reg_lock),					\
> +		.lock_enable_bit = (_lock_enable_bit),				\
> +	}
> +
> +#define CCU_PLL_HWINIT(_name, _flags)						\
> +	(&(struct clk_init_data) {						\
> +		.name		= #_name,					\
> +		.ops		= &spacemit_ccu_pll_ops,			\
> +		.parent_data	= &(struct clk_parent_data) { .index = 0 },	\
> +		.num_parents	= 1,						\
> +		.flags		= _flags,					\
> +	})
> +
> +#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3,			\
> +		       _reg_lock, _lock_enable_bit, _flags)			\

static const struct ccu_pll _name = { \

					-Alex

> +	struct ccu_pll _name = {						\
> +		.pll	= CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit),	\
> +		.common = {							\
> +			.reg_swcr1	= _reg_swcr1,				\
> +			.reg_swcr3	= _reg_swcr3,				\
> +			.hw.init	= CCU_PLL_HWINIT(_name, _flags)		\
> +		}								\
> +	}
> +
> +static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
> +{
> +	struct ccu_common *common = hw_to_ccu_common(hw);
> +
> +	return container_of(common, struct ccu_pll, common);
> +}
> +
> +extern const struct clk_ops spacemit_ccu_pll_ops;
> +
> +#endif
	

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
  2025-03-07  0:51   ` Yixun Lan
  2025-03-11 23:19   ` Alex Elder
@ 2025-03-12 20:17   ` kernel test robot
  2025-03-18  5:37   ` Yixun Lan
  3 siblings, 0 replies; 26+ messages in thread
From: kernel test robot @ 2025-03-12 20:17 UTC (permalink / raw)
  To: Haylen Chu, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: oe-kbuild-all, linux-riscv, linux-clk, devicetree, linux-kernel,
	spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang

Hi Haylen,

kernel test robot noticed the following build warnings:

[auto build test WARNING on spacemit/for-next]
[also build test WARNING on spacemit/fixes clk/clk-next robh/for-next linus/master v6.14-rc6 next-20250312]
[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/Haylen-Chu/dt-bindings-soc-spacemit-Add-spacemit-k1-syscon/20250307-020635
base:   https://github.com/spacemit-com/linux for-next
patch link:    https://lore.kernel.org/r/20250306175750.22480-5-heylenay%404d2.org
patch subject: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
config: powerpc64-randconfig-r131-20250312 (https://download.01.org/0day-ci/archive/20250313/202503130314.Y8KFKZWW-lkp@intel.com/config)
compiler: clang version 18.1.8 (https://github.com/llvm/llvm-project 3b5b5c1ec4a3095ab096dd780e84d7ab81f3d7ff)
reproduce: (https://download.01.org/0day-ci/archive/20250313/202503130314.Y8KFKZWW-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/202503130314.Y8KFKZWW-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> drivers/clk/spacemit/ccu_pll.c:110:9: sparse: sparse: cast truncates bits from constant value (ffffffff7fffffff becomes 7fffffff)

vim +110 drivers/clk/spacemit/ccu_pll.c

    85	
    86	/*
    87	 * PLLs must be gated before changing rate, which is ensured by
    88	 * flag CLK_SET_RATE_GATE.
    89	 */
    90	static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
    91				    unsigned long parent_rate)
    92	{
    93		struct ccu_pll *p = hw_to_ccu_pll(hw);
    94		struct ccu_common *common = &p->common;
    95		struct ccu_pll_config *params = &p->pll;
    96		const struct ccu_pll_rate_tbl *entry = NULL;
    97		int i;
    98	
    99		for (i = 0; i < params->tbl_size; i++) {
   100			if (rate == params->rate_tbl[i].rate) {
   101				entry = &params->rate_tbl[i];
   102				break;
   103			}
   104		}
   105	
   106		if (WARN_ON_ONCE(!entry))
   107			return -EINVAL;
   108	
   109		ccu_update(swcr1, common, entry->swcr1, entry->swcr1);
 > 110		ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3);
   111	
   112		return 0;
   113	}
   114	

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

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
                     ` (2 preceding siblings ...)
  2025-03-12 20:17   ` kernel test robot
@ 2025-03-18  5:37   ` Yixun Lan
  2025-03-18  5:43     ` Inochi Amaoto
  3 siblings, 1 reply; 26+ messages in thread
From: Yixun Lan @ 2025-03-18  5:37 UTC (permalink / raw)
  To: Haylen Chu
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

Hi Haylen Chu:

On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> The clock tree of K1 SoC contains three main types of clock hardware
> (PLL/DDN/MIX) and has control registers split into several multifunction
> devices: APBS (PLLs), MPMU, APBC and APMU.
> 
> All register operations are done through regmap to ensure atomiciy
> between concurrent operations of clock driver and reset,
> power-domain driver that will be introduced in the future.
> 
> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> ---
>  drivers/clk/Kconfig               |    1 +
>  drivers/clk/Makefile              |    1 +
>  drivers/clk/spacemit/Kconfig      |   20 +
>  drivers/clk/spacemit/Makefile     |    5 +
>  drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
>  drivers/clk/spacemit/ccu_common.h |   47 +
>  drivers/clk/spacemit/ccu_ddn.c    |   80 ++
>  drivers/clk/spacemit/ccu_ddn.h    |   48 +
>  drivers/clk/spacemit/ccu_mix.c    |  284 +++++
>  drivers/clk/spacemit/ccu_mix.h    |  246 +++++
>  drivers/clk/spacemit/ccu_pll.c    |  146 +++
>  drivers/clk/spacemit/ccu_pll.h    |   76 ++
>  12 files changed, 2668 insertions(+)
>  create mode 100644 drivers/clk/spacemit/Kconfig
>  create mode 100644 drivers/clk/spacemit/Makefile
>  create mode 100644 drivers/clk/spacemit/ccu-k1.c
>  create mode 100644 drivers/clk/spacemit/ccu_common.h
>  create mode 100644 drivers/clk/spacemit/ccu_ddn.c
>  create mode 100644 drivers/clk/spacemit/ccu_ddn.h
>  create mode 100644 drivers/clk/spacemit/ccu_mix.c
>  create mode 100644 drivers/clk/spacemit/ccu_mix.h
>  create mode 100644 drivers/clk/spacemit/ccu_pll.c
>  create mode 100644 drivers/clk/spacemit/ccu_pll.h
> 
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 713573b6c86c..19c1ed280fd7 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
>  source "drivers/clk/sifive/Kconfig"
>  source "drivers/clk/socfpga/Kconfig"
>  source "drivers/clk/sophgo/Kconfig"
> +source "drivers/clk/spacemit/Kconfig"
>  source "drivers/clk/sprd/Kconfig"
>  source "drivers/clk/starfive/Kconfig"
>  source "drivers/clk/sunxi/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index bf4bd45adc3a..42867cd37c33 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
>  obj-$(CONFIG_CLK_SIFIVE)		+= sifive/
>  obj-y					+= socfpga/
>  obj-y					+= sophgo/
> +obj-y					+= spacemit/
>  obj-$(CONFIG_PLAT_SPEAR)		+= spear/
>  obj-y					+= sprd/
>  obj-$(CONFIG_ARCH_STI)			+= st/
> diff --git a/drivers/clk/spacemit/Kconfig b/drivers/clk/spacemit/Kconfig
> new file mode 100644
> index 000000000000..76090cd85668
> --- /dev/null
> +++ b/drivers/clk/spacemit/Kconfig
> @@ -0,0 +1,20 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config SPACEMIT_CCU
> +	tristate "Clock support for Spacemit SoCs"
> +	default y
similar reason to pinctrl with these patches [1], [2]
I'd suggest switch to "bool + default ARCH_SPACEMIT" 

Link: https://lore.kernel.org/all/20250218-k1-pinctrl-option-v3-1-36e031e0da1b@gentoo.org [1]
Link: https://lore.kernel.org/all/6881b8d1ad74ac780af8a974e604b5ef3f5d4aad.1742198691.git.geert+renesas@glider.be [2]

> +	depends on ARCH_SPACEMIT || COMPILE_TEST
> +	select MFD_SYSCON
> +	help
> +	  Say Y to enable clock controller unit support for Spacemit SoCs.
> +
> +if SPACEMIT_CCU
> +
> +config SPACEMIT_K1_CCU
> +	tristate "Support for Spacemit K1 SoC"
> +	default y
ditto
> +	depends on ARCH_SPACEMIT || COMPILE_TEST
> +	help
> +	  Support for clock controller unit in Spacemit K1 SoC.
> +
> +endif

-- 
Yixun Lan (dlan)
Gentoo Linux Developer
GPG Key ID AABEFD55

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-18  5:37   ` Yixun Lan
@ 2025-03-18  5:43     ` Inochi Amaoto
  2025-03-23  8:55       ` Haylen Chu
  0 siblings, 1 reply; 26+ messages in thread
From: Inochi Amaoto @ 2025-03-18  5:43 UTC (permalink / raw)
  To: Yixun Lan, Haylen Chu
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

On Tue, Mar 18, 2025 at 05:37:36AM +0000, Yixun Lan wrote:
> Hi Haylen Chu:
> 
> On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> > The clock tree of K1 SoC contains three main types of clock hardware
> > (PLL/DDN/MIX) and has control registers split into several multifunction
> > devices: APBS (PLLs), MPMU, APBC and APMU.
> > 
> > All register operations are done through regmap to ensure atomiciy
> > between concurrent operations of clock driver and reset,
> > power-domain driver that will be introduced in the future.
> > 
> > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> > ---
> >  drivers/clk/Kconfig               |    1 +
> >  drivers/clk/Makefile              |    1 +
> >  drivers/clk/spacemit/Kconfig      |   20 +
> >  drivers/clk/spacemit/Makefile     |    5 +
> >  drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
> >  drivers/clk/spacemit/ccu_common.h |   47 +
> >  drivers/clk/spacemit/ccu_ddn.c    |   80 ++
> >  drivers/clk/spacemit/ccu_ddn.h    |   48 +
> >  drivers/clk/spacemit/ccu_mix.c    |  284 +++++
> >  drivers/clk/spacemit/ccu_mix.h    |  246 +++++
> >  drivers/clk/spacemit/ccu_pll.c    |  146 +++
> >  drivers/clk/spacemit/ccu_pll.h    |   76 ++
> >  12 files changed, 2668 insertions(+)
> >  create mode 100644 drivers/clk/spacemit/Kconfig
> >  create mode 100644 drivers/clk/spacemit/Makefile
> >  create mode 100644 drivers/clk/spacemit/ccu-k1.c
> >  create mode 100644 drivers/clk/spacemit/ccu_common.h
> >  create mode 100644 drivers/clk/spacemit/ccu_ddn.c
> >  create mode 100644 drivers/clk/spacemit/ccu_ddn.h
> >  create mode 100644 drivers/clk/spacemit/ccu_mix.c
> >  create mode 100644 drivers/clk/spacemit/ccu_mix.h
> >  create mode 100644 drivers/clk/spacemit/ccu_pll.c
> >  create mode 100644 drivers/clk/spacemit/ccu_pll.h
> > 
> > diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> > index 713573b6c86c..19c1ed280fd7 100644
> > --- a/drivers/clk/Kconfig
> > +++ b/drivers/clk/Kconfig
> > @@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
> >  source "drivers/clk/sifive/Kconfig"
> >  source "drivers/clk/socfpga/Kconfig"
> >  source "drivers/clk/sophgo/Kconfig"
> > +source "drivers/clk/spacemit/Kconfig"
> >  source "drivers/clk/sprd/Kconfig"
> >  source "drivers/clk/starfive/Kconfig"
> >  source "drivers/clk/sunxi/Kconfig"
> > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> > index bf4bd45adc3a..42867cd37c33 100644
> > --- a/drivers/clk/Makefile
> > +++ b/drivers/clk/Makefile
> > @@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
> >  obj-$(CONFIG_CLK_SIFIVE)		+= sifive/
> >  obj-y					+= socfpga/
> >  obj-y					+= sophgo/
> > +obj-y					+= spacemit/
> >  obj-$(CONFIG_PLAT_SPEAR)		+= spear/
> >  obj-y					+= sprd/
> >  obj-$(CONFIG_ARCH_STI)			+= st/
> > diff --git a/drivers/clk/spacemit/Kconfig b/drivers/clk/spacemit/Kconfig
> > new file mode 100644
> > index 000000000000..76090cd85668
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/Kconfig
> > @@ -0,0 +1,20 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +
> > +config SPACEMIT_CCU
> > +	tristate "Clock support for Spacemit SoCs"
> > +	default y
> similar reason to pinctrl with these patches [1], [2]
> I'd suggest switch to "bool + default ARCH_SPACEMIT" 
> 
> Link: https://lore.kernel.org/all/20250218-k1-pinctrl-option-v3-1-36e031e0da1b@gentoo.org [1]
> Link: https://lore.kernel.org/all/6881b8d1ad74ac780af8a974e604b5ef3f5d4aad.1742198691.git.geert+renesas@glider.be [2]
> 

Clk subsystem prefers no defalt and set it in defconfig,
so should no default there.

Regards,
Inochi

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-11 23:19   ` Alex Elder
@ 2025-03-20 22:39     ` Alex Elder
  2025-03-24 11:14     ` Haylen Chu
  1 sibling, 0 replies; 26+ messages in thread
From: Alex Elder @ 2025-03-20 22:39 UTC (permalink / raw)
  To: Haylen Chu, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu

On 3/11/25 6:19 PM, Alex Elder wrote:
> On 3/6/25 11:57 AM, Haylen Chu wrote:
>> The clock tree of K1 SoC contains three main types of clock hardware
>> (PLL/DDN/MIX) and has control registers split into several multifunction
>> devices: APBS (PLLs), MPMU, APBC and APMU.
>>
>> All register operations are done through regmap to ensure atomiciy
>> between concurrent operations of clock driver and reset,
>> power-domain driver that will be introduced in the future.
>>
>> Signed-off-by: Haylen Chu <heylenay@4d2.org>
> 
> I'm very glad you have the DT issues resolved now.
> 
> I again have lots of comments on the code, and I think I've
> identified a few bugs.  Most of my comments, however, are
> suggesting minor changes for consistency and readability.
> 
> I'm going to skip over a lot of "ccu-k1.c" because most of what I
> say applies to the definitions in the header files.

FYI I encountered a problem I mentioned below.

. . .

>> +/* frequency unit Mhz, return pll vco freq */
>> +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
>> +{
>> +    const struct ccu_pll_rate_tbl *pll_rate_table;
>> +    struct ccu_pll *p = hw_to_ccu_pll(hw);
>> +    struct ccu_common *common = &p->common;
>> +    u32 swcr1, swcr3, size;
>> +    int i;
>> +
>> +    ccu_read(swcr1, common, &swcr1);
>> +    ccu_read(swcr3, common, &swcr3);
> 
> You are masking off the EN bit, but you should really be
> using a mask defining which bits are valid instead.  As
> I said earlier:
> 
> #define SPACEMIT_PLL_SWCR3_MASK    ~(SPACEMIT_PLL_SWCR3_EN)
> 
>> +    swcr3 &= ~PLL_SWCR3_EN;
> 
>      swcr3 &= SPACEMIT_PLL_SWCR3_MASK;
>> +
>> +    pll_rate_table = p->pll.rate_tbl;
>> +    size = p->pll.tbl_size;
>> +
>> +    for (i = 0; i < size; i++) {
>> +        if (pll_rate_table[i].swcr1 == swcr1 &&
>> +            pll_rate_table[i].swcr3 == swcr3)
>> +            return pll_rate_table[i].rate;
>> +    }
>> +
> 
> I have a general question here.  Once you set one of these
> clock rates, it will always use one of the rates defined
> in the table.
> 
> But what about initially?  Could the hardware start in a
> state that is not defined by this code?  Do you *set* the
> rate initially?  Should you (at least the first time the
> clock is prepared/enabled)?

When doing some testing today I found that the WARN_ON_ONCE()
got called.  I added some information and learned that the
values in hardware of the swcr1 and swcr3 registers were:
   swcr1:  0x0050cd61
   swcr3:  0x3fe00000
I'm not sure which PLL was being used.

So clearly this can happen.  Somehow you need to find a way
to ensure that these registers are initialized to a sane
state (meaning one defined within pll_rate_table[]).

					-Alex

>> +    WARN_ON_ONCE(1);
> 
> Maybe WARN_ONCE(true, "msg");. . .

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-18  5:43     ` Inochi Amaoto
@ 2025-03-23  8:55       ` Haylen Chu
  0 siblings, 0 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-23  8:55 UTC (permalink / raw)
  To: Inochi Amaoto, Yixun Lan
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Haylen Chu, linux-riscv, linux-clk, devicetree,
	linux-kernel, spacemit, Inochi Amaoto, Chen Wang, Jisheng Zhang,
	Meng Zhang

On Tue, Mar 18, 2025 at 01:43:52PM +0800, Inochi Amaoto wrote:
> On Tue, Mar 18, 2025 at 05:37:36AM +0000, Yixun Lan wrote:
> > Hi Haylen Chu:
> > 
> > On 17:57 Thu 06 Mar     , Haylen Chu wrote:
> > > The clock tree of K1 SoC contains three main types of clock hardware
> > > (PLL/DDN/MIX) and has control registers split into several multifunction
> > > devices: APBS (PLLs), MPMU, APBC and APMU.
> > > 
> > > All register operations are done through regmap to ensure atomiciy
> > > between concurrent operations of clock driver and reset,
> > > power-domain driver that will be introduced in the future.
> > > 
> > > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> > > ---
> > >  drivers/clk/Kconfig               |    1 +
> > >  drivers/clk/Makefile              |    1 +
> > >  drivers/clk/spacemit/Kconfig      |   20 +
> > >  drivers/clk/spacemit/Makefile     |    5 +
> > >  drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
> > >  drivers/clk/spacemit/ccu_common.h |   47 +
> > >  drivers/clk/spacemit/ccu_ddn.c    |   80 ++
> > >  drivers/clk/spacemit/ccu_ddn.h    |   48 +
> > >  drivers/clk/spacemit/ccu_mix.c    |  284 +++++
> > >  drivers/clk/spacemit/ccu_mix.h    |  246 +++++
> > >  drivers/clk/spacemit/ccu_pll.c    |  146 +++
> > >  drivers/clk/spacemit/ccu_pll.h    |   76 ++
> > >  12 files changed, 2668 insertions(+)
> > >  create mode 100644 drivers/clk/spacemit/Kconfig
> > >  create mode 100644 drivers/clk/spacemit/Makefile
> > >  create mode 100644 drivers/clk/spacemit/ccu-k1.c
> > >  create mode 100644 drivers/clk/spacemit/ccu_common.h
> > >  create mode 100644 drivers/clk/spacemit/ccu_ddn.c
> > >  create mode 100644 drivers/clk/spacemit/ccu_ddn.h
> > >  create mode 100644 drivers/clk/spacemit/ccu_mix.c
> > >  create mode 100644 drivers/clk/spacemit/ccu_mix.h
> > >  create mode 100644 drivers/clk/spacemit/ccu_pll.c
> > >  create mode 100644 drivers/clk/spacemit/ccu_pll.h
> > > 
> > > diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> > > index 713573b6c86c..19c1ed280fd7 100644
> > > --- a/drivers/clk/Kconfig
> > > +++ b/drivers/clk/Kconfig
> > > @@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
> > >  source "drivers/clk/sifive/Kconfig"
> > >  source "drivers/clk/socfpga/Kconfig"
> > >  source "drivers/clk/sophgo/Kconfig"
> > > +source "drivers/clk/spacemit/Kconfig"
> > >  source "drivers/clk/sprd/Kconfig"
> > >  source "drivers/clk/starfive/Kconfig"
> > >  source "drivers/clk/sunxi/Kconfig"
> > > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> > > index bf4bd45adc3a..42867cd37c33 100644
> > > --- a/drivers/clk/Makefile
> > > +++ b/drivers/clk/Makefile
> > > @@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG)	+= samsung/
> > >  obj-$(CONFIG_CLK_SIFIVE)		+= sifive/
> > >  obj-y					+= socfpga/
> > >  obj-y					+= sophgo/
> > > +obj-y					+= spacemit/
> > >  obj-$(CONFIG_PLAT_SPEAR)		+= spear/
> > >  obj-y					+= sprd/
> > >  obj-$(CONFIG_ARCH_STI)			+= st/
> > > diff --git a/drivers/clk/spacemit/Kconfig b/drivers/clk/spacemit/Kconfig
> > > new file mode 100644
> > > index 000000000000..76090cd85668
> > > --- /dev/null
> > > +++ b/drivers/clk/spacemit/Kconfig
> > > @@ -0,0 +1,20 @@
> > > +# SPDX-License-Identifier: GPL-2.0-only
> > > +

Hi Inochi, Yixun,

> > > +config SPACEMIT_CCU
> > > +	tristate "Clock support for Spacemit SoCs"
> > > +	default y
> > similar reason to pinctrl with these patches [1], [2]
> > I'd suggest switch to "bool + default ARCH_SPACEMIT" 
> > 
> > Link: https://lore.kernel.org/all/20250218-k1-pinctrl-option-v3-1-36e031e0da1b@gentoo.org [1]
> > Link: https://lore.kernel.org/all/6881b8d1ad74ac780af8a974e604b5ef3f5d4aad.1742198691.git.geert+renesas@glider.be [2]
> > 
> 
> Clk subsystem prefers no defalt and set it in defconfig,
> so should no default there.

Thanks for these hints, I will drop default and set the configuration in
defconfig, in which case it seems okay to keep it as bool.

> Regards,
> Inochi

Thanks,
Haylen Chu

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-11 23:19   ` Alex Elder
  2025-03-20 22:39     ` Alex Elder
@ 2025-03-24 11:14     ` Haylen Chu
  2025-03-28 14:00       ` Alex Elder
  1 sibling, 1 reply; 26+ messages in thread
From: Haylen Chu @ 2025-03-24 11:14 UTC (permalink / raw)
  To: Alex Elder, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu

On Tue, Mar 11, 2025 at 06:19:51PM -0500, Alex Elder wrote:
> On 3/6/25 11:57 AM, Haylen Chu wrote:
> > The clock tree of K1 SoC contains three main types of clock hardware
> > (PLL/DDN/MIX) and has control registers split into several multifunction
> > devices: APBS (PLLs), MPMU, APBC and APMU.
> > 
> > All register operations are done through regmap to ensure atomiciy
> > between concurrent operations of clock driver and reset,
> > power-domain driver that will be introduced in the future.
> > 
> > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> 
> I'm very glad you have the DT issues resolved now.
> 
> I again have lots of comments on the code, and I think I've
> identified a few bugs.  Most of my comments, however, are
> suggesting minor changes for consistency and readability.
> 
> I'm going to skip over a lot of "ccu-k1.c" because most of what I
> say applies to the definitions in the header files.
> 
> > ---
> >   drivers/clk/Kconfig               |    1 +
> >   drivers/clk/Makefile              |    1 +
> >   drivers/clk/spacemit/Kconfig      |   20 +
> >   drivers/clk/spacemit/Makefile     |    5 +
> >   drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
> >   drivers/clk/spacemit/ccu_common.h |   47 +
> >   drivers/clk/spacemit/ccu_ddn.c    |   80 ++
> >   drivers/clk/spacemit/ccu_ddn.h    |   48 +
> >   drivers/clk/spacemit/ccu_mix.c    |  284 +++++
> >   drivers/clk/spacemit/ccu_mix.h    |  246 +++++
> >   drivers/clk/spacemit/ccu_pll.c    |  146 +++
> >   drivers/clk/spacemit/ccu_pll.h    |   76 ++
> >   12 files changed, 2668 insertions(+)
> >   create mode 100644 drivers/clk/spacemit/Kconfig
> >   create mode 100644 drivers/clk/spacemit/Makefile
> >   create mode 100644 drivers/clk/spacemit/ccu-k1.c
> >   create mode 100644 drivers/clk/spacemit/ccu_common.h
> >   create mode 100644 drivers/clk/spacemit/ccu_ddn.c
> >   create mode 100644 drivers/clk/spacemit/ccu_ddn.h
> >   create mode 100644 drivers/clk/spacemit/ccu_mix.c
> >   create mode 100644 drivers/clk/spacemit/ccu_mix.h
> >   create mode 100644 drivers/clk/spacemit/ccu_pll.c
> >   create mode 100644 drivers/clk/spacemit/ccu_pll.h
> > 

...

> > diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
> > new file mode 100644
> > index 000000000000..5974a0a1b5f6
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu-k1.c
> > @@ -0,0 +1,1714 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> > + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/delay.h>
> > +#include <linux/mfd/syscon.h>
> > +#include <linux/minmax.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +
> > +#include "ccu_common.h"
> > +#include "ccu_pll.h"
> > +#include "ccu_mix.h"
> > +#include "ccu_ddn.h"
> > +
> > +#include <dt-bindings/clock/spacemit,k1-ccu.h>
> > +
> > +/*	APBS register offset	*/
> 
> Use spaces, not tabs above (same as you've done elsewhere).
> 

Ack. I'll unify all the comments to use TABs as you mentioned below.

> > +#define APBS_PLL1_SWCR1			0x100
> > +#define APBS_PLL1_SWCR2			0x104
> > +#define APBS_PLL1_SWCR3			0x108
> > +#define APBS_PLL2_SWCR1			0x118
> > +#define APBS_PLL2_SWCR2			0x11c
> > +#define APBS_PLL2_SWCR3			0x120
> > +#define APBS_PLL3_SWCR1			0x124
> > +#define APBS_PLL3_SWCR2			0x128
> > +#define APBS_PLL3_SWCR3			0x12c
> > +
> > +/* MPMU register offset */
> > +#define MPMU_POSR			0x10
> > +#define POSR_PLL1_LOCK			BIT(27)
> > +#define POSR_PLL2_LOCK			BIT(28)
> > +#define POSR_PLL3_LOCK			BIT(29)
> > +
> > +#define MPMU_WDTPCR			0x200
> > +#define MPMU_RIPCCR			0x210
> > +#define MPMU_ACGR			0x1024
> > +#define MPMU_SUCCR			0x14
> > +#define MPMU_ISCCR			0x44
> 
> Somehow the fact that you used just two digits for the above
> two made me think maybe they were field values rather than
> offsets.
> 
> If you used 4 digits for all offsets (with leading 0's) maybe
> that consistency would reinforce the difference between an
> offset and other values.
> 
> Also, please sort the offset definitions in increasing
> numeric order (within the groups); that too will help
> readability.

Thanks, I'll take them.

> > +#define MPMU_SUCCR_1			0x10b0
> > +#define MPMU_APBCSCR			0x1050
> > +
> > +/* APBC register offset */
> > +#define APBC_UART1_CLK_RST		0x0
> > +#define APBC_UART2_CLK_RST		0x4
> > +#define APBC_GPIO_CLK_RST		0x8
> 
> . . .
> 
> > +#define APBC_PWM17_CLK_RST		0xdc
> > +#define APBC_PWM18_CLK_RST		0xe0
> > +#define APBC_PWM19_CLK_RST		0xe4
> > +
> 
> I realize that these symbols are named based on the
> underlying hardware register names.  But the above
> uses "CLK_RST" to indicate a clock/reset control
> register.  But the definitions below spell "reset"
> differently:  "CLK_RES".
> What do you think about using a consistent naming scheme in the code?

This of course sounds reasonable to me. But I think the following names
are originated from the datasheet and renaming them may makes it harder
to search through the datasheet. I'm willing to change them if you think
it's not a problem.

> > +/* APMU register offset */
> > +#define APMU_JPG_CLK_RES_CTRL		0x20
> > +#define APMU_CSI_CCIC2_CLK_RES_CTRL	0x24
> > +#define APMU_ISP_CLK_RES_CTRL		0x38
> > +#define APMU_LCD_CLK_RES_CTRL1		0x44
> > +#define APMU_LCD_SPI_CLK_RES_CTRL	0x48
> > +#define APMU_LCD_CLK_RES_CTRL2		0x4c
> > +#define APMU_CCIC_CLK_RES_CTRL		0x50
> > +#define APMU_SDH0_CLK_RES_CTRL		0x54
> > +#define APMU_SDH1_CLK_RES_CTRL		0x58
> > +#define APMU_USB_CLK_RES_CTRL		0x5c
> > +#define APMU_QSPI_CLK_RES_CTRL		0x60
> > +#define APMU_DMA_CLK_RES_CTRL		0x64
> > +#define APMU_AES_CLK_RES_CTRL		0x68
> > +#define APMU_VPU_CLK_RES_CTRL		0xa4
> > +#define APMU_GPU_CLK_RES_CTRL		0xcc
> > +#define APMU_SDH2_CLK_RES_CTRL		0xe0
> > +#define APMU_PMUA_MC_CTRL		0xe8
> > +#define APMU_PMU_CC2_AP			0x100
> > +#define APMU_PMUA_EM_CLK_RES_CTRL	0x104
> > +#define APMU_AUDIO_CLK_RES_CTRL		0x14c
> > +#define APMU_HDMI_CLK_RES_CTRL		0x1b8
> > +#define APMU_CCI550_CLK_CTRL		0x300
> > +#define APMU_ACLK_CLK_CTRL		0x388
> > +#define APMU_CPU_C0_CLK_CTRL		0x38C
> > +#define APMU_CPU_C1_CLK_CTRL		0x390
> > +#define APMU_PCIE_CLK_RES_CTRL_0	0x3cc
> > +#define APMU_PCIE_CLK_RES_CTRL_1	0x3d4
> > +#define APMU_PCIE_CLK_RES_CTRL_2	0x3dc
> > +#define APMU_EMAC0_CLK_RES_CTRL		0x3e4
> > +#define APMU_EMAC1_CLK_RES_CTRL		0x3ec
> > +
> > +/*	APBS clocks start	*/
> > +
> > +/* Frequency of pll{1,2} should not be updated at runtime */
> 
> Given the clock tables below only define a single entry,
> I don't believe it's even possible to update pll{1,2} at
> runtime.  Maybe the above comment could go away.  If
> you keep it, I'd say "must not" rather than "should not."

Okay, I'll improve the comment, which explains why only one entry
exists.

> > +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = {
> > +	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
> > +};
> > +
> > +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = {
> > +	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
> > +};
> > +
> > +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = {
> > +	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
> > +	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
> > +	CCU_PLL_RATE(3200000000UL, 0x0050dd67, 0x43eaaaab),
> > +};
> > +
> 
> I'm going to restate this later.  But I think you can include
> "static" in the definition of your CCU_*_DEFINE() macros,
> because all defined clocks will be static.  I believe they
> can also get the const qualifier here.

I'm not sure about the const qualifier, but it's definitely okay to move
static into the macros. I'll improve it in next version.

> > +static CCU_PLL_DEFINE(pll1, pll1_rate_tbl,
> > +		      APBS_PLL1_SWCR1, APBS_PLL1_SWCR3,
> > +		      MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE);
> > +static CCU_PLL_DEFINE(pll2, pll2_rate_tbl,
> > +		      APBS_PLL2_SWCR1, APBS_PLL2_SWCR3,
> > +		      MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE);
> > +static CCU_PLL_DEFINE(pll3, pll3_rate_tbl,
> > +		      APBS_PLL3_SWCR1, APBS_PLL2_SWCR3,
> > +		      MPMU_POSR, POSR_PLL3_LOCK, CLK_SET_RATE_GATE);
> > +
> > +static CCU_GATE_FACTOR_DEFINE(pll1_d2, CCU_PARENT_HW(pll1),
> > +			      APBS_PLL1_SWCR2,
> > +			      BIT(1), 2, 1, 0);
> 
> You seem to try to use multiple lines for these definitions
> to possibly aid readability.  I think that's a good idea in
> general.  But it isn't really consistent (I'll point out
> a few examples below), and so it doesn't always work.  For
> many (maybe all), the first line is consistent, but the rest
> seem to vary a bit.
> 
> These definitions are basically hardware definitions.  So
> assuming you get them all correct initially, they'll never
> change (for a given platform, like K1), and readability is
> less of an issue.
> 
> This time around I haven't scrutinized these in that much
> detail.  But I think--once you're confident the definitions
> are correct--you can simply format all of these clock
> definitions to fit within 80 columns, and save a bunch of
> lines in the source file.

Yes, sounds reasonable to me. I will do the reformatting if there's no
objection against it.

> > +static CCU_GATE_FACTOR_DEFINE(pll1_d3, CCU_PARENT_HW(pll1),
> > +			      APBS_PLL1_SWCR2,
> > +			      BIT(2), 3, 1, 0);
> 
> . . .
> 
> > +static CCU_GATE_FACTOR_DEFINE(pll3_d8, CCU_PARENT_HW(pll3),
> > +			      APBS_PLL3_SWCR2,
> > +			      BIT(7), 8, 1, 0);
> > +
> 
> Here (below) you define these PLL "factor" clocks all on one line.
> 
> > +static CCU_FACTOR_DEFINE(pll3_20, CCU_PARENT_HW(pll3_d8), 20, 1);
> > +static CCU_FACTOR_DEFINE(pll3_40, CCU_PARENT_HW(pll3_d8), 10, 1);
> > +static CCU_FACTOR_DEFINE(pll3_80, CCU_PARENT_HW(pll3_d8), 5, 1);
> > +
> > +/*	APBS clocks end		*/
> 
> Use spaces rather than tabs, above and below (and throughout).
> Or...  Use tabs consistently.  Pick one.
> 
> > +
> > +/*	MPMU clocks start	*/
> > +static CCU_GATE_DEFINE(pll1_d8_307p2, CCU_PARENT_HW(pll1_d8),
> > +		       MPMU_ACGR,
> > +		       BIT(13), 0);
> 
> But here you define the "factor" clock on two lines.  I'm sure
> this is for line length.  I'd say just define them all according
> to line length--make them fit within 80 columns, but otherwise
> don't bother with extra lines for readability.
> 
> > +static CCU_FACTOR_DEFINE(pll1_d32_76p8, CCU_PARENT_HW(pll1_d8_307p2),
> > +			 4, 1);
> 
> . . .
> 
> > +
> > +static const struct clk_parent_data apb_parents[] = {
> > +	CCU_PARENT_HW(pll1_d96_25p6),
> > +	CCU_PARENT_HW(pll1_d48_51p2),
> > +	CCU_PARENT_HW(pll1_d96_25p6),
> > +	CCU_PARENT_HW(pll1_d24_102p4),
> > +};
> 
> I think you should have a blank line after this definition (and
> all other similar ones in this file).  In some cases you have
> no blank line above this sort of definition, and you should.

These definitions are grouped by types and parents/children: similar
clocks of the same type, like PWM ones, are kept together; possible
parents and their children are kept together, too. IMHO it's more
readable when looking through the clocks, but checkpatch.pl issues some
suggestions about it as well. Anyway, I'm willing to make the changes.

> > +static CCU_MUX_DEFINE(apb_clk, apb_parents,
> > +		      MPMU_APBCSCR,
> > +		      0, 2,
> > +		      0);
> > +
> > +static CCU_GATE_DEFINE(wdt_bus_clk, CCU_PARENT_HW(apb_clk),
> > +		       MPMU_WDTPCR,
> > +		       BIT(0),
> > +		       0);
> > +
> > +static CCU_GATE_DEFINE(ripc_clk, CCU_PARENT_HW(apb_clk),
> > +		       MPMU_RIPCCR,
> > +		       0x1,
> > +		       0);
> > +/*	MPMU clocks end		*/
> > +
> > +/*	APBC clocks start	*/
> > +static const struct clk_parent_data uart_clk_parents[] = {
> > +	CCU_PARENT_HW(pll1_m3d128_57p6),
> > +	CCU_PARENT_HW(slow_uart1_14p74),
> > +	CCU_PARENT_HW(slow_uart2_48),
> > +};
> 
> Here you're putting the control register and flags field on
> separate lines.  I'd say just join them.

I'll reformat all clocks, only following the 80-columns rule, so similar
separation shouldn't be in next version.

> > +static CCU_MUX_GATE_DEFINE(uart0_clk, uart_clk_parents,
> > +			   APBC_UART1_CLK_RST,
> > +			   4, 3, BIT(1),
> > +			   CLK_IS_CRITICAL);
> 
> . . .
> 
> > +struct spacemit_ccu_clk {
> > +	int id;
> > +	struct clk_hw *hw;
> > +};
> > +
> 
> The following array defines the clocks under what is matched
> as "spacemit,k1-pll".  I know it's the "apbs" (APB Spare)
> memory region, but I think it would be better to have the
> name here match the compatible string.  Add a comment if
> you want to make sure people understand the "PLL<->APBS"
> relationship.

Thanks, will take it.

> > +static struct spacemit_ccu_clk k1_ccu_apbs_clks[] = {
> > +	{ CLK_PLL1,		&pll1.common.hw },
> > +	{ CLK_PLL2,		&pll2.common.hw },
> > +	{ CLK_PLL3,		&pll3.common.hw },
> 
> . . .
> 
> > +static int spacemit_ccu_register(struct device *dev,
> > +				 struct regmap *regmap, struct regmap *lock_regmap,
> > +				 const struct spacemit_ccu_clk *clks)
> > +{
> > +	const struct spacemit_ccu_clk *clk;
> > +	int i, ret, max_id = 0;
> > +
> > +	for (clk = clks; clk->hw; clk++)
> > +		max_id = max(max_id, clk->id);
> > +
> > +	struct clk_hw_onecell_data *clk_data;
> 
> The definition of clk_data belongs at the top of the function.

Oops, I miss it. Will fix the style.

> > +
> > +	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, max_id + 1), GFP_KERNEL);
> > +	if (!clk_data)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i <= max_id; i++)
> > +		clk_data->hws[i] = ERR_PTR(-ENOENT);
> > +
> > +	for (clk = clks; clk->hw; clk++) {
> > +		struct ccu_common *common = hw_to_ccu_common(clk->hw);
> > +		const char *name = clk->hw->init->name;
> > +
> > +		common->regmap		= regmap;
> > +		common->lock_regmap	= lock_regmap;
> > +
> > +		ret = devm_clk_hw_register(dev, clk->hw);
> > +		if (ret) {
> > +			dev_err(dev, "Cannot register clock %d - %s\n",
> > +				i, name);
> > +			return ret;
> > +		}
> > +
> > +		clk_data->hws[clk->id] = clk->hw;
> > +	}
> > +
> > +	clk_data->num = max_id + 1;
> > +
> > +	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
> > +}
> > +
> > +static int k1_ccu_probe(struct platform_device *pdev)
> > +{
> > +	struct regmap *base_regmap, *lock_regmap = NULL;
> > +	struct device *dev = &pdev->dev;
> > +	int ret;
> > +
> > +	base_regmap = device_node_to_regmap(dev->of_node);
> > +	if (IS_ERR(base_regmap))
> > +		return dev_err_probe(dev, PTR_ERR(base_regmap),
> > +				     "failed to get regmap\n");
> > +
> 
> You should add a comment here explaining why there is a special
> additional regmap for the PLL clock type (because it requires a
> lock that's found in that that device node's memory region).

I guess there's already some explanation in the dt-bindings, but I'm
willing to add some more :)

...

> > diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
> > new file mode 100644
> > index 000000000000..494cde96fe3c
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_common.h
> > @@ -0,0 +1,47 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> > + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> > + */
> > +
> > +#ifndef _CCU_COMMON_H_
> > +#define _CCU_COMMON_H_
> > +
> > +#include <linux/regmap.h>
> > +
> 
> I'm not going to suggest it at this point, but it might
> have worked out more nicely if you defined a top-level CCU
> structure that contained a union of structs, one for each
> type of clock (PLL, DDN, mix).
> 
> > +struct ccu_common {
> > +	struct regmap *regmap;
> > +	struct regmap *lock_regmap;
> 
> The lock_regmap is only used for PLL type clocks, right?
> So it could be included in the PLL struct within the union
> below?

This makes sense to me.

> > +
> > +	union {
> > +		/* For DDN and MIX */
> > +		struct {
> > +			u32 reg_ctrl;
> > +			u32 reg_fc;
> > +			u32 fc;
> 
> The fc field is a bit mask, and with a single bit set.
> 
> Other fields you define use the convention "mask" in the name
> to indicate it is a bit mask.  So I suggest you name "fc" to
> be "mask_fc" or "fc_mask" (but in the latter case, I'd rename
> the registers to be "fc_reg" and "ctrl_reg"--you decide).

I prefer the former.

> Since fc is a (nonzero) mask, you could use a zero fc value
> to indicate that reg_fc is the same as reg_ctrl.

I'm not sure about what you mean and what the change could improve: even
in case that reg_fc is the same as reg_ctrl, we still require a field to
store the mask for FC bit.

Knowing reg_ctrl is just reg_fc doesn't help much: we could directly
assign reg_ctrl to reg_fc in our macros and avoid extra logic in
ccu_mix.c.

> > +		};
> > +
> > +		/* For PLL */
> > +		struct {
> > +			u32 reg_swcr1;
> > +			u32 reg_swcr2;
> 
> The reg_swcr2 is no longer used (now that you've dropped it
> from the ccu_pll_rate_tbl structure).

Will drop it. I think it was missed during cleanup.

> > +			u32 reg_swcr3;
> 
> You define PLL_SWCR3_EN in "ccu_pll.c" to have value BIT(31).
> that's good.  But you should define its inverse, to define
> which bits in the reg_swcr3 field are the valid "magic" part.
> In both cases, I would define them here in this file, where
> the structure type is defined (not in "ccu_pll.c").
> #define SPACEMIT_PLL_SWCR3_EN	(u32)BIT(31)
> #define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)
> 
> > +		};
> > +	};
> > +
> > +	struct clk_hw hw;
> > +};
> > +
> > +static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
> > +{
> > +	return container_of(hw, struct ccu_common, hw);
> > +}
> > +
> 
> This isn't a huge deal, but I think since your functions here
> are named ccu_*(), the first argument should be the CCU (or
> ccu_common) structure.
> 
> > +#define ccu_read(reg, c, val)	regmap_read((c)->regmap, (c)->reg_##reg, val)
> 
> I commented before that you're not checking return values for
> regmap_read() and regmap_write().  You said you don't expect
> MMIO accesses to ever fail.
> 
> If we assume that, I'd like to see ccu_read() defined differently,
> so that it returns a value (rather than taking the assigned-to
> address as an argument).  To me, it makes it much easier to
> understand in places it's used, making it more obvious an
> assignment is getting made.

Thanks for the suggesion,

> static inline u32 _ccu_read(struct ccu_common *common, u32 offset)
> {
> 	u32 val;
> 
> 	(void)regmap_read(common->regmap, offset>reg_##reg, &val);
> 
> 	return val;
> }
> #define ccu_read(reg, c)	_ccu_read((c), (c)->reg_##reg)
> 
> 	val = ccu_read(ctrl, &ddn->common);
> 

And we may avoid the extra inline function with GNU compound statement,
just like how read_poll_timeout_atomic() is defined.

> > +#define ccu_update(reg, c, mask, val) \
> > +	regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)7
> > +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \
> > +	regmap_read_poll_timeout_atomic((c)->regmap, (c)->reg_##reg,	\
> > +					tmp, cond, sleep, timeout)
> > +
> > +#endif /* _CCU_COMMON_H_ */
> > diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c
> > new file mode 100644
> > index 000000000000..ee187687d0c4
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_ddn.c
> > @@ -0,0 +1,80 @@

...

> > +static unsigned long clk_ddn_calc_best_rate(struct ccu_ddn *ddn,
> > +					    unsigned long rate, unsigned long prate,
> > +					    unsigned long *num, unsigned long *den)
> > +{
> > +	rational_best_approximation(rate, prate / 2,
> > +				    ddn->den_mask, ddn->num_mask,
> > +				    den, num);
> 
> Using rational_best_approximation() is excellent.  However I
> think you have a bug, and I don't think the exact way you're
> using it is clear (and might be wrong).
> 
> The bug is that the third and fourth arguments are the maximum
> numerator and denominator, respectively.  You are passing mask
> values, which in some sense represent the maximums.  However,
> your masks are not always in the low-order bits.  Here is one
> example:
> 
> static CCU_DDN_DEFINE(slow_uart1_14p74, pll1_d16_153p6,
>                       MPMU_SUCCR,
>                       GENMASK(28, 16), 16, GENMASK(12, 0), 0,
>                       0);
> 
> The "_num_mask" argument to this macro is 0x1fff0000, and the
> "_den_mask" is 0x00000fff.  The latter value (which gets passed
> as the max_numerator argument to rational_best_approximation())
> is fine, but the former is not.  So you need to shift both masks
> right by their corresponding shift value.

Thanks for finding it! I forget to change the function when redefining
struct ccu_ddn.

> Beyond that bug, rational_best_approximation() wants its first
> two arguments to define the desired rate (as a fraction).

I don't think it's the way that rational_best_approximation() works.
The first two arguments define the desired fraction, not the rate.

> So the desired rate should be the actual desired rate divided by 1
> (rather than being divided by the half the parent rate).  So
> this too might be a bug.

From the comment in its implementation,

  * calculate best rational approximation for a given fraction
  * taking into account restricted register size

And we have

            numerator       Fin
      2 * ------------- = -------
           denominator      Fout

divide two on both sides,

            numerator       Fin / 2
          ------------- = -----------
           denominator       Fout

So we could take rational_best_approximation() to calculate the best
approximated num and den, where give_numerator = Fin / 2 = prate / 2,
given_denominator = Fout = rate, best_numerator = numerator = &num and
best_denominator = denominator = &den. My code swaps den and num when
passing them to rational_best_approximation(), but this doesn't affect
the result and I'll fix it in next version.

> Maybe I'm misinterpreting this, but the bottom line is I'm
> pretty certain this is not producing the correct numerator
> and denominator that would produce the closest rate supported
> by the hardware.
> 
> > +	return prate / 2 * *den / *num;
> 
> The above matches your explanation of how the output rate
> is computed based on the "denominator" and "numerator".
> You could encapsulate simply that in a function, given
> tnat you used it here and another time below.
> 
> You might get slightly better precision by deferring the
> divide-by-2.

Thanks, will take them.

...

> > diff --git a/drivers/clk/spacemit/ccu_ddn.h b/drivers/clk/spacemit/ccu_ddn.h
> > new file mode 100644
> > index 000000000000..3746d084e1e7
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_ddn.h
> > @@ -0,0 +1,48 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> > + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> > + */
> > +
> > +#ifndef _CCU_DDN_H_
> > +#define _CCU_DDN_H_
> > +
> > +#include <linux/clk-provider.h>
> > +
> > +#include "ccu_common.h"
> > +
> > +struct ccu_ddn {
> > +	struct ccu_common common;
> > +	unsigned int num_mask;
> > +	unsigned int num_shift;
> > +	unsigned int den_mask;
> > +	unsigned int den_shift;
> > +};
> > +
> > +#define CCU_DDN_INIT(_name, _parent, _flags) \
> > +	CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
> > +
> 
> In CCU_DDN_DEFINE(), both the numerator and denominator masks
> consist of a contiguous block of 1 bits, with zero or more 0
> bits to their left and right.  They are always constant (both
> times it's used...).
> 
> For that reason, I'd like you to drop the _num_shift and _den_shift
> arguments from this macro.  Instead, use __ffs(_num_mask) and
> __ffs(_den_mask) when assigning the num_shift and den_shift field
> values.

Thanks, it's reasonable to me.

...

> > diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c
> > new file mode 100644
> > index 000000000000..a5c13000e062
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_mix.c
> > @@ -0,0 +1,284 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Spacemit clock type mix(div/mux/gate/factor)
> > + *
> > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> > + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +
> > +#include "ccu_mix.h"
> > +
> 
> I think the name of this constant should include "FC", and a
> comment should indicate it is a number of microseconds. "FC"
> stands for "frequency change"; I think you should have a comment
> explaining exactly what that means (somewhere).

This macro was also used for a polling after gate is enabled, but I've
confirmed with the vendor that it's unnecessary. Seems I forgot to
rename it.

I prefer to give it a self-explained name, like MIX_FC_TIMEOUT_US, and
explain the FC stuff above ccu_mix_trigger_fc() in comments.

> > +#define MIX_TIMEOUT	10000
> > +
> > +static void ccu_gate_disable(struct clk_hw *hw)
> > +{
> > +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> > +	struct ccu_common *common = &mix->common;
> > +
> > +	ccu_update(ctrl, common, mix->gate.mask, 0);
> > +}
> 
> Maybe drop the second local variable and do:
> 
> 	ccu_update(ctrl, &mix->common, mix->gate.mask, 0);
> 
> And this can be done several times below.

Makes sense to me.

> > +
> > +static int ccu_gate_enable(struct clk_hw *hw)
> > +{
> > +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> > +	struct ccu_common *common = &mix->common;
> > +	struct ccu_gate_config *gate = &mix->gate;
> > +
> > +	ccu_update(ctrl, common, gate->mask, gate->mask);
> > +
> > +	return 0;
> > +}
> > +
> > +static int ccu_gate_is_enabled(struct clk_hw *hw)
> > +{
> > +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> > +	struct ccu_common *common = &mix->common;
> > +	u32 tmp;
> > +
> > +	ccu_read(ctrl, common, &tmp);
> > +
> 
> The next line won't necessarily work.  The gate mask
> value in a few cases contains more than one set bit
> (rtc_clk is an exmaple).  Therefore this will return
> true even if just one of those bits is set.  This is
> most likely a bug.
> 
> This needs to be:
> 
> 	return (tmp & mix->gete.mask) == mix->gate.mask;

Oops, thanks, I didn't expect the case.

> > +	return !!(tmp & mix->gate.mask);
> > +}
> > +

...

> > +static unsigned long
> > +ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
> > +		       struct clk_hw **best_parent,
> > +		       unsigned long *best_parent_rate,
> > +		       u32 *div_val)
> > +{
> > +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> > +	unsigned int parent_num = clk_hw_get_num_parents(hw);
> > +	struct ccu_div_config *div = &mix->div;
> > +	u32 div_max = 1 << div->width;
> > +	unsigned long best_rate = 0;
> > +
> > +	for (int i = 0; i < parent_num; i++) {
> > +		struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
> > +		unsigned long parent_rate;
> > +
> > +		if (!parent)
> > +			continue;
> > +
> > +		parent_rate = clk_hw_get_rate(parent);
> > +
> > +		for (int j = 1; j <= div_max; j++) {
> > +			unsigned long tmp = DIV_ROUND_UP_ULL(parent_rate, j);
> 
> I might have asked this before.  Why round up?  Why not
> round closest?
> 

Rounding to closest should be correct here, as determine_rate() is
expected to return the closest rate, thanks for pointing out this.

> > +
> > +			if (abs(tmp - rate) < abs(best_rate - rate)) {
> > +				best_rate = tmp;
> > +
> > +				if (div_val)
> > +					*div_val = j - 1;
> > +
> > +				if (best_parent) {
> > +					*best_parent      = parent;
> > +					*best_parent_rate = parent_rate;
> > +				}
> > +			}
> > +		}
> > +	}
> > +
> > +	return best_rate;
> > +}

...

> > +static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
> > +{
> > +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
> > +	struct ccu_common *common = &mix->common;
> > +	struct ccu_mux_config *mux = &mix->mux;
> > +	int ret = 0;
> > +	u32 mask;
> > +
> 
> I'm not sure it will ever happen, but if index is out of range
> this most likely won't do the right thing.
> 
> 	if (WARN_ON(index >= 1 << mux->width))
> 		return -EINVAL;

I don't think such case is possible, since all indexes passed in
set_parent() callback are calculated by clk_parent_index, which always
produces a valid index or error code if we correctly set num_parents.

> > +	mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
> > +
> > +	ccu_update(ctrl, common, mask, index << mux->shift);
> > +
> > +	if (common->reg_fc)
> > +		ret = ccu_mix_trigger_fc(hw);
> > +
> > +	return ret;
> > +}
> > +
> 
> I think your naming for these should follow a pattern, for
> example:
>   gate then (factor or div) then mux then fc

Okay, mux then factor/div then gate then fc sounds better to me, since
it reflects the order of these hardwares as shown in the clock diagram.

I guess this should apply for CCU_*_DEFINE() macros as well, right?

> > +const struct clk_ops spacemit_ccu_gate_ops = {
> > +	.disable	= ccu_gate_disable,
> > +	.enable		= ccu_gate_enable,
> > +	.is_enabled	= ccu_gate_is_enabled,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_factor_ops = {
> > +	.round_rate	= ccu_factor_round_rate,
> > +	.recalc_rate	= ccu_factor_recalc_rate,
> > +	.set_rate	= ccu_factor_set_rate,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_mux_ops = {
> > +	.determine_rate = ccu_mix_determine_rate,
> > +	.get_parent	= ccu_mux_get_parent,
> > +	.set_parent	= ccu_mux_set_parent,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_div_ops = {
> > +	.determine_rate = ccu_mix_determine_rate,
> > +	.recalc_rate	= ccu_div_recalc_rate,
> > +	.set_rate	= ccu_mix_set_rate,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_gate_factor_ops = {
> > +	.disable	= ccu_gate_disable,
> > +	.enable		= ccu_gate_enable,
> > +	.is_enabled	= ccu_gate_is_enabled,
> > +
> > +	.round_rate	= ccu_factor_round_rate,
> > +	.recalc_rate	= ccu_factor_recalc_rate,
> > +	.set_rate	= ccu_factor_set_rate,
> > +};
> > +
> 
> If you follow the pattern I said above, this would be
> spacemit_ccu_gate_mux_ops().
> 
> (And so on.)
> 
> > +const struct clk_ops spacemit_ccu_mux_gate_ops = {
> > +	.disable	= ccu_gate_disable,
> > +	.enable		= ccu_gate_enable,
> > +	.is_enabled	= ccu_gate_is_enabled,
> > +
> > +	.determine_rate = ccu_mix_determine_rate,
> > +	.get_parent	= ccu_mux_get_parent,
> > +	.set_parent	= ccu_mux_set_parent,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_div_gate_ops = {
> > +	.disable	= ccu_gate_disable,
> > +	.enable		= ccu_gate_enable,
> > +	.is_enabled	= ccu_gate_is_enabled,
> > +
> > +	.determine_rate = ccu_mix_determine_rate,
> > +	.recalc_rate	= ccu_div_recalc_rate,
> > +	.set_rate	= ccu_mix_set_rate,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_div_mux_gate_ops = {
> > +	.disable	= ccu_gate_disable,
> > +	.enable		= ccu_gate_enable,
> > +	.is_enabled	= ccu_gate_is_enabled,
> > +
> > +	.get_parent	= ccu_mux_get_parent,
> > +	.set_parent	= ccu_mux_set_parent,
> > +
> > +	.determine_rate = ccu_mix_determine_rate,
> > +	.recalc_rate	= ccu_div_recalc_rate,
> > +	.set_rate	= ccu_mix_set_rate,
> > +};
> > +
> > +const struct clk_ops spacemit_ccu_div_mux_ops = {
> > +	.get_parent	= ccu_mux_get_parent,
> > +	.set_parent	= ccu_mux_set_parent,
> > +
> > +	.determine_rate = ccu_mix_determine_rate,
> > +	.recalc_rate	= ccu_div_recalc_rate,
> > +	.set_rate	= ccu_mix_set_rate,
> > +};
> > diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h
> > new file mode 100644
> > index 000000000000..a3aa292d073d
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_mix.h

...

> I think you should define these next two with their types.
> 
> > +#define CCU_PARENT_HW(_parent)		{ .hw = &_parent.common.hw }
> > +#define CCU_PARENT_NAME(_name)		{ .fw_name = #_name }
> > +

It's not necessary since there're enough context for the compiler in all
use cases. It's okay for me if you consider it's better to introduce
some extra clearness,

> #define CCU_PARENT_HW(_parent) \
> 	(struct clk_parent_data){ .hw = &_parent.common.hw }
> 
> > +#define CCU_MIX_INITHW(_name, _parent, _ops, _flags)			\
> > +	(&(struct clk_init_data) {					\
> 
> I'm pretty sure the left parenthesis above and its matching
> right parenthesis below are not needed.

CCU_MIX_INIT_HW is defined just like CLK_HW_INIT_HW(), which includes
these parenthesis. I think they're for guarding usage of macros and not
needed for our definition. Will remove them.

> > +		.flags		= _flags,				\
> > +		.name		= #_name,				\
> 
> If the type is defined above, you don't need to cast it here.

It's not a cast, it's a compound literal[1] introduced in C99, which
cannot be dropped even if we define the type above.

> Also the curly braces around _parent look strange to me.  The
> parent_data field is a pointer, and ideally there should be
> no need to use a type cast here.

parent_data is a scalar pointer instead of a compound type, thus we
cannot define its value by simply putting a pair of braces. We have
two options here,

1. Specify the type in CCU_PARENT_{HW,NAME}, make it a compound literal
   and taking its address in CCU_MIX_INITHW()

	#define CCU_PARENT_HW(_parent) \
		(struct clk_parent_data){ .hw = &_parent.common.hw }

	/* in CCU_MIX_INITHW() */
		.parent_data = &_parent,

2. Create an anonymous array by compound literal in CCU_MIX_INITHW() and
   take its address. Members of the array are compound types, thus it's
   okay to omit the type in CCU_PARENT_{HW,NAME}.

Option 2 is our current solution and also what standard ccf macros
(CLK_HW_INIT_HW and etc.) look like. I cannot decide which is better.

> > +		.parent_data	= (const struct clk_parent_data[])	\
> > +					{ _parent },			\
> > +		.num_parents	= 1,					\
> > +		.ops		= &_ops,				\
> > +	})
> > +
> > +#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags)		\
> > +	CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
> > +
> 
> Many/most of the other CCU_*_DEFINE() macros use the name
> _ctrl_reg for the register.  I suggest you use that here
> (and everywhere, for consistency).

I will take it.

> > +#define CCU_GATE_DEFINE(_name, _parent, _reg, _gate_mask, _flags)		\
> 
> static const struct ccu_mix _name = { \
> 
> > +struct ccu_mix _name = {							\
> > +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> > +	.common	= {								\
> > +		.reg_ctrl	= _reg,						\
> 
> Why do you have a tab before the equal sign here?  (But not
> elsewhere?)  Whatever you decide, be consistent.

It's just for saving some lines: CCU_MIX_INITHW() seems too long and
causes early line breaks. Is it acceptable to move the whole assignement
into CCU_MIX_INITHW() macro? This should look better.

> > +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> > +					  spacemit_ccu_gate_ops, _flags),	\
> > +	}									\
> > +}
> > +
> > +#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul)				\
> 
> static const struct ccu_mix _name = { \

As discussed in ccu-k1.c, I'll take the change.

> > +struct ccu_mix _name = {							\
> > +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
> > +	.common = {								\
> > +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> > +					  spacemit_ccu_factor_ops, 0),		\
> > +	}									\
> > +}

...

> > +#define CCU_GATE_FACTOR_DEFINE(_name, _parent,					\
> > +			       _reg,						\
> > +			       _gate_mask,					\
> > +			       _div, _mul,					\
> > +			       _flags)						\
> 
> static const struct ccu_mix _name = { \
> 
> I believe the value of _flags is always zero, so that argument
> could be eliminated (it can be added back in the future if it's
> ever needed).

Makes sense to me, I'm willing to take it.

> There are only two cases where the value of _mul is different
> from 1.  You could define a slightly different macro for those
> cases, and for this, remove the multiplier argument.

I don't think it's worth introducing an extra macro for saving only two
or three types, we already have a lot of them.

> > +struct ccu_mix _name = {							\
> > +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> > +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
> > +	.common = {								\
> > +		.reg_ctrl	= _reg,						\
> > +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
> > +					  spacemit_ccu_gate_factor_ops, _flags)	\
> > +	}									\
> > +}
> > +

...

> I think I might have said this elsewhere;  use _fc_mask
> (or something similar) to define what you call _fc here.

Yes, in ccu_common.h. I'll take it.

> > +#define CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents,			\
> > +					 _reg_ctrl, _reg_fc,			\
> > +					 _mshift, _mwidth,			\
> > +					 _fc,					\
> > +					 _muxshift, _muxwidth,			\
> > +					 _gate_mask,				\
> > +					 _flags)				\
> 
> static const struct ccu_mix _name = { \
> 
> > +struct ccu_mix _name = {							\
> > +	.gate	= CCU_GATE_INIT(_gate_mask),					\
> > +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> > +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> > +	.common = {								\
> > +		.reg_ctrl	= _reg_ctrl,					\
> > +		.reg_fc		= _reg_fc,					\
> > +		.fc		= _fc,						\
> 
> In all cases, these FC masks have a single bit set; this is
> something I think is worth mentioning in a comment.

I think ccu_mix_trigger_fc() seems a good place for it: we could explain
the FC machanism and the special nature of the fc (fc_mask) field
together.

> > +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> > +						  spacemit_ccu_div_mux_gate_ops,\
> > +						  _flags),			\
> > +	},									\
> > +}
> > +

...

> static const struct ccu_mix _name = { \
> 
> > +struct ccu_mix _name = {							\
> > +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
> > +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
> > +	.common = {								\
> > +		.reg_ctrl	= _reg_ctrl,					\
> > +		.reg_fc		= _reg_ctrl,					\
> 
> I suggested elsewhere that you could use a non-zero FC mask
> to indicate the FC register is the same as the reg_ctrl
> register.  (Despite my repeated suggestion, what you're
> doing already is fine...)

Please refer to my question about the non-zero FC mask in ccu_common.h.

> > +		.fc		= _fc,						\
> > +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
> > +						  spacemit_ccu_div_mux_ops,	\
> > +						  _flags),			\
> > +	},									\
> > +}

...

> I think you can define each ops variable on its own line.

I will take it, which makes the code cleaner.

> > +extern const struct clk_ops spacemit_ccu_gate_ops, spacemit_ccu_factor_ops;
> > +extern const struct clk_ops spacemit_ccu_mux_ops, spacemit_ccu_div_ops;
> > +
> > +extern const struct clk_ops spacemit_ccu_gate_factor_ops;
> > +extern const struct clk_ops spacemit_ccu_div_gate_ops;
> > +extern const struct clk_ops spacemit_ccu_mux_gate_ops;
> > +extern const struct clk_ops spacemit_ccu_div_mux_ops;
> > +
> > +extern const struct clk_ops spacemit_ccu_div_mux_gate_ops;
> > +#endif /* _CCU_DIV_H_ */
> > diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
> > new file mode 100644
> > index 000000000000..9df2149f6c98
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_pll.c
> > @@ -0,0 +1,146 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Spacemit clock type pll
> > + *
> > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> > + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> > + */
> > +
> > +#include <linux/clk-provider.h>
> > +#include <linux/regmap.h>
> > +
> > +#include "ccu_common.h"
> > +#include "ccu_pll.h"
> > +
> 
> Name this just PLL_TIMEOUT, or maybe CCU_PLL_TIMEOUT, and
> indicate in a comment that it is a period in microseconds.

Same for the FC timeout case, I'd like to append a "_US" suffix to
indicate its unit.

...

> > +/* frequency unit Mhz, return pll vco freq */
> > +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
> > +{
> > +	const struct ccu_pll_rate_tbl *pll_rate_table;
> > +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> > +	struct ccu_common *common = &p->common;
> > +	u32 swcr1, swcr3, size;
> > +	int i;
> > +
> > +	ccu_read(swcr1, common, &swcr1);
> > +	ccu_read(swcr3, common, &swcr3);
> 
> You are masking off the EN bit, but you should really be
> using a mask defining which bits are valid instead.  As
> I said earlier:
> 
> #define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)
> 
> > +	swcr3 &= ~PLL_SWCR3_EN;
> 
> 	swcr3 &= SPACEMIT_PLL_SWCR3_MASK;
> > +
> > +	pll_rate_table = p->pll.rate_tbl;
> > +	size = p->pll.tbl_size;
> > +
> > +	for (i = 0; i < size; i++) {
> > +		if (pll_rate_table[i].swcr1 == swcr1 &&
> > +		    pll_rate_table[i].swcr3 == swcr3)
> > +			return pll_rate_table[i].rate;
> > +	}
> > +
> 
> I have a general question here.  Once you set one of these
> clock rates, it will always use one of the rates defined
> in the table.
> 
> But what about initially?  Could the hardware start in a
> state that is not defined by this code?  Do you *set* the
> rate initially?  Should you (at least the first time the
> clock is prepared/enabled)?

Thanks, I've also seen your later report. Here we may support
clk_ops.init and reinitialize the PLL if it's not in a good state.

Could you please provide a possible reproducing scenario for me to test
against the PLL problem?

> > +	WARN_ON_ONCE(1);
> 
> Maybe WARN_ONCE(true, "msg");
> 
> > +
> > +	return 0;
> > +}
> > +
> > +static int ccu_pll_enable(struct clk_hw *hw)
> > +{
> > +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> > +	struct ccu_common *common = &p->common;
> > +	unsigned int tmp;
> > +	int ret;
> > +
> 
> Get rid of ret (see below).
> 
> Will clk_ops->enable() ever be called when it's already
> enabled?  (If it won't, this isn't needed.  If it will,
> this checks the hardware, which is good.)

CCF holds a refcounter of clock consumers, so we could drop the check.

> > +	if (ccu_pll_is_enabled(hw))
> > +		return 0;
> > +
> > +	ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN);
> > +
> > +	/* check lock status */
> > +	ret = regmap_read_poll_timeout_atomic(common->lock_regmap,
> > +					      p->pll.reg_lock,
> > +					      tmp,
> > +					      tmp & p->pll.lock_enable_bit,
> > +					      5, PLL_DELAY_TIME);
> 
> Just:
> 
> 	return regmap_read_poll_timeout_atomic(...);
> 
> I note that you call this here, but you hide the call
> to regmap_read_poll_timeout_atomic() behind the macro
> ccu_poll().  And ccu_poll() (used for the FC bit) is
> also only called once.
> 
> I suggest you get rid of regmap_poll() and just open-code
> it.
> 
> (You use ccu_read() and ccu_update() numerous times, so
> your "saving some characters" is justified.)

Makes sense to me, will take it.

> > +
> > +	return ret;
> > +}

...

> > +/*
> > + * PLLs must be gated before changing rate, which is ensured by
> > + * flag CLK_SET_RATE_GATE.
> > + */
> > +static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
> > +			    unsigned long parent_rate)
> > +{
> > +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> > +	struct ccu_common *common = &p->common;
> > +	struct ccu_pll_config *params = &p->pll;
> > +	const struct ccu_pll_rate_tbl *entry = NULL;
> > +	int i;
> > +
> > +	for (i = 0; i < params->tbl_size; i++) {
> > +		if (rate == params->rate_tbl[i].rate) {
> > +			entry = &params->rate_tbl[i];
> > +			break;
> > +		}
> > +	}
> > +
> > +	if (WARN_ON_ONCE(!entry))
> > +		return -EINVAL;
> > +
> 
> The next line contains a bug.  The third argument defines
> a mask of which bits get updated.  Therefore, any zero bits
> in that mask will *not* get updated in the swcr1 register
> even if they should be.
> 
> Example:
> Old SWCR1:	0x01234567
> New SWCR1:	0x00112233
> 
> Updated:	0x01336777
> Want:		0x00112233
> 
> You should either define ccu_write() (my preference), or
> you you should call:

Or maybe we could just call regmap_write directly here, it's the only
place we require unprotected writes (PLL registers aren't shared) and
it's not worth introducing the macro again.

> 	ccu_update(swcr1, common, ~0, entry->swcr1);
> 
> 
> > +	ccu_update(swcr1, common, entry->swcr1, entry->swcr1);
> 
> You should use SPACEMIT_PLL_SWCR3_MASK below.  No
> cast should be needed (define the symbol with one).
> 
> > +	ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3);
> > +
> > +	return 0;
> > +}

...

> > +static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> > +			       unsigned long *prate)
> > +{
> > +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> > +	struct ccu_pll_config *params = &p->pll;
> > +	unsigned int i;
> > +
> 
> I'm pretty sure I mentioned this before.  If you cant find a
> matching rate, you return the closest rate *less than* what
> is requested, which might not be as close as the closest
> rate *greater than* the requested rate.

I don't see CCF requires round_rate to yield a rate that is greater than
the request value, but only a closest one. Am i missing something?

> In ccu_mix_calc_best_rate() you actually take this into account
> (though you use DIV_ROUND_UP_ULL() there too).
> 
> > +	for (i = 0; i < params->tbl_size; i++) {
> > +		if (params->rate_tbl[i].rate > rate) {
> > +			i--;
> > +			break;
> > +		}
> > +	}
> > +
> > +	return rate;
> > +}

...

> > diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h
> > new file mode 100644
> > index 000000000000..c6a3a5cce995
> > --- /dev/null
> > +++ b/drivers/clk/spacemit/ccu_pll.h

...

> > +struct ccu_pll_config {
> > +	const struct ccu_pll_rate_tbl *rate_tbl;
> > +	u32 tbl_size;
> 
> I dislike "size" as a name for something that is a "count".
> To me, "size" is a number of bytes.
> 
> You (and clock code) use "num" elsewhere, but either way,
> please use something different from "size" here.

Of course "num" works for me.

Thank you for the detailed review! I'm pretty busy these days and
delayed my reply. I'll try to send a v7 this week with all the comments
resolved.

Best regards,
Haylen Chu

[1]: https://en.cppreference.com/w/c/language/compound_literal


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-24 11:14     ` Haylen Chu
@ 2025-03-28 14:00       ` Alex Elder
  2025-03-29 10:21         ` Haylen Chu
  0 siblings, 1 reply; 26+ messages in thread
From: Alex Elder @ 2025-03-28 14:00 UTC (permalink / raw)
  To: Haylen Chu, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu

On 3/24/25 6:14 AM, Haylen Chu wrote:
> On Tue, Mar 11, 2025 at 06:19:51PM -0500, Alex Elder wrote:
>> On 3/6/25 11:57 AM, Haylen Chu wrote:
>>> The clock tree of K1 SoC contains three main types of clock hardware
>>> (PLL/DDN/MIX) and has control registers split into several multifunction
>>> devices: APBS (PLLs), MPMU, APBC and APMU.
>>>
>>> All register operations are done through regmap to ensure atomiciy
>>> between concurrent operations of clock driver and reset,
>>> power-domain driver that will be introduced in the future.
>>>
>>> Signed-off-by: Haylen Chu <heylenay@4d2.org>
>>
>> I'm very glad you have the DT issues resolved now.
>>
>> I again have lots of comments on the code, and I think I've
>> identified a few bugs.  Most of my comments, however, are
>> suggesting minor changes for consistency and readability.
>>
>> I'm going to skip over a lot of "ccu-k1.c" because most of what I
>> say applies to the definitions in the header files.

Sorry I didn't respond to this earlier.

>>> ---
>>>    drivers/clk/Kconfig               |    1 +
>>>    drivers/clk/Makefile              |    1 +
>>>    drivers/clk/spacemit/Kconfig      |   20 +
>>>    drivers/clk/spacemit/Makefile     |    5 +
>>>    drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
>>>    drivers/clk/spacemit/ccu_common.h |   47 +
>>>    drivers/clk/spacemit/ccu_ddn.c    |   80 ++
>>>    drivers/clk/spacemit/ccu_ddn.h    |   48 +
>>>    drivers/clk/spacemit/ccu_mix.c    |  284 +++++
>>>    drivers/clk/spacemit/ccu_mix.h    |  246 +++++
>>>    drivers/clk/spacemit/ccu_pll.c    |  146 +++
>>>    drivers/clk/spacemit/ccu_pll.h    |   76 ++
>>>    12 files changed, 2668 insertions(+)
>>>    create mode 100644 drivers/clk/spacemit/Kconfig
>>>    create mode 100644 drivers/clk/spacemit/Makefile
>>>    create mode 100644 drivers/clk/spacemit/ccu-k1.c
>>>    create mode 100644 drivers/clk/spacemit/ccu_common.h
>>>    create mode 100644 drivers/clk/spacemit/ccu_ddn.c
>>>    create mode 100644 drivers/clk/spacemit/ccu_ddn.h
>>>    create mode 100644 drivers/clk/spacemit/ccu_mix.c
>>>    create mode 100644 drivers/clk/spacemit/ccu_mix.h
>>>    create mode 100644 drivers/clk/spacemit/ccu_pll.c
>>>    create mode 100644 drivers/clk/spacemit/ccu_pll.h
>>>
> 
> ...
> 
>>> diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
>>> new file mode 100644
>>> index 000000000000..5974a0a1b5f6
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu-k1.c
>>> @@ -0,0 +1,1714 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
>>> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
>>> + */
>>> +
>>> +#include <linux/clk-provider.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/mfd/syscon.h>
>>> +#include <linux/minmax.h>
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +
>>> +#include "ccu_common.h"
>>> +#include "ccu_pll.h"
>>> +#include "ccu_mix.h"
>>> +#include "ccu_ddn.h"
>>> +
>>> +#include <dt-bindings/clock/spacemit,k1-ccu.h>
>>> +
>>> +/*	APBS register offset	*/
>>
>> Use spaces, not tabs above (same as you've done elsewhere).
>>
> 
> Ack. I'll unify all the comments to use TABs as you mentioned below.
> 
>>> +#define APBS_PLL1_SWCR1			0x100
>>> +#define APBS_PLL1_SWCR2			0x104
>>> +#define APBS_PLL1_SWCR3			0x108
>>> +#define APBS_PLL2_SWCR1			0x118
>>> +#define APBS_PLL2_SWCR2			0x11c
>>> +#define APBS_PLL2_SWCR3			0x120
>>> +#define APBS_PLL3_SWCR1			0x124
>>> +#define APBS_PLL3_SWCR2			0x128
>>> +#define APBS_PLL3_SWCR3			0x12c
>>> +
>>> +/* MPMU register offset */
>>> +#define MPMU_POSR			0x10
>>> +#define POSR_PLL1_LOCK			BIT(27)
>>> +#define POSR_PLL2_LOCK			BIT(28)
>>> +#define POSR_PLL3_LOCK			BIT(29)
>>> +
>>> +#define MPMU_WDTPCR			0x200
>>> +#define MPMU_RIPCCR			0x210
>>> +#define MPMU_ACGR			0x1024
>>> +#define MPMU_SUCCR			0x14
>>> +#define MPMU_ISCCR			0x44
>>
>> Somehow the fact that you used just two digits for the above
>> two made me think maybe they were field values rather than
>> offsets.
>>
>> If you used 4 digits for all offsets (with leading 0's) maybe
>> that consistency would reinforce the difference between an
>> offset and other values.
>>
>> Also, please sort the offset definitions in increasing
>> numeric order (within the groups); that too will help
>> readability.
> 
> Thanks, I'll take them.
> 
>>> +#define MPMU_SUCCR_1			0x10b0
>>> +#define MPMU_APBCSCR			0x1050
>>> +
>>> +/* APBC register offset */
>>> +#define APBC_UART1_CLK_RST		0x0
>>> +#define APBC_UART2_CLK_RST		0x4
>>> +#define APBC_GPIO_CLK_RST		0x8
>>
>> . . .
>>
>>> +#define APBC_PWM17_CLK_RST		0xdc
>>> +#define APBC_PWM18_CLK_RST		0xe0
>>> +#define APBC_PWM19_CLK_RST		0xe4
>>> +
>>
>> I realize that these symbols are named based on the
>> underlying hardware register names.  But the above
>> uses "CLK_RST" to indicate a clock/reset control
>> register.  But the definitions below spell "reset"
>> differently:  "CLK_RES".
>> What do you think about using a consistent naming scheme in the code?
> 
> This of course sounds reasonable to me. But I think the following names
> are originated from the datasheet and renaming them may makes it harder
> to search through the datasheet. I'm willing to change them if you think
> it's not a problem.

Sticking to the names found in hardware documentation is
an easy rule to follow.  But sometimes when it's gathered
in code you see where things are inconsistent, which makes
the code less understandable.

The easiest is to just stick with the names you currently
use, which is fine with me.  There should probably be a
compelling reason to use different names, and in this case
the "RST" versus "RES" isn't super important.

Ideally every name you use should match what's in the
hardware documentation.  You could do a quick check for
any such differences.

In short:  what you use is fine; ignore my suggestion.

>>> +/* APMU register offset */
>>> +#define APMU_JPG_CLK_RES_CTRL		0x20
>>> +#define APMU_CSI_CCIC2_CLK_RES_CTRL	0x24
>>> +#define APMU_ISP_CLK_RES_CTRL		0x38
>>> +#define APMU_LCD_CLK_RES_CTRL1		0x44
>>> +#define APMU_LCD_SPI_CLK_RES_CTRL	0x48
>>> +#define APMU_LCD_CLK_RES_CTRL2		0x4c
>>> +#define APMU_CCIC_CLK_RES_CTRL		0x50
>>> +#define APMU_SDH0_CLK_RES_CTRL		0x54
>>> +#define APMU_SDH1_CLK_RES_CTRL		0x58
>>> +#define APMU_USB_CLK_RES_CTRL		0x5c
>>> +#define APMU_QSPI_CLK_RES_CTRL		0x60
>>> +#define APMU_DMA_CLK_RES_CTRL		0x64
>>> +#define APMU_AES_CLK_RES_CTRL		0x68
>>> +#define APMU_VPU_CLK_RES_CTRL		0xa4
>>> +#define APMU_GPU_CLK_RES_CTRL		0xcc
>>> +#define APMU_SDH2_CLK_RES_CTRL		0xe0
>>> +#define APMU_PMUA_MC_CTRL		0xe8
>>> +#define APMU_PMU_CC2_AP			0x100
>>> +#define APMU_PMUA_EM_CLK_RES_CTRL	0x104
>>> +#define APMU_AUDIO_CLK_RES_CTRL		0x14c
>>> +#define APMU_HDMI_CLK_RES_CTRL		0x1b8
>>> +#define APMU_CCI550_CLK_CTRL		0x300
>>> +#define APMU_ACLK_CLK_CTRL		0x388
>>> +#define APMU_CPU_C0_CLK_CTRL		0x38C
>>> +#define APMU_CPU_C1_CLK_CTRL		0x390
>>> +#define APMU_PCIE_CLK_RES_CTRL_0	0x3cc
>>> +#define APMU_PCIE_CLK_RES_CTRL_1	0x3d4
>>> +#define APMU_PCIE_CLK_RES_CTRL_2	0x3dc
>>> +#define APMU_EMAC0_CLK_RES_CTRL		0x3e4
>>> +#define APMU_EMAC1_CLK_RES_CTRL		0x3ec
>>> +
>>> +/*	APBS clocks start	*/
>>> +
>>> +/* Frequency of pll{1,2} should not be updated at runtime */
>>
>> Given the clock tables below only define a single entry,
>> I don't believe it's even possible to update pll{1,2} at
>> runtime.  Maybe the above comment could go away.  If
>> you keep it, I'd say "must not" rather than "should not."
> 
> Okay, I'll improve the comment, which explains why only one entry
> exists.
> 
>>> +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = {
>>> +	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
>>> +};
>>> +
>>> +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = {
>>> +	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
>>> +};
>>> +
>>> +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = {
>>> +	CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd),
>>> +	CCU_PLL_RATE(3000000000UL, 0x0050dd66, 0x3fe00000),
>>> +	CCU_PLL_RATE(3200000000UL, 0x0050dd67, 0x43eaaaab),
>>> +};
>>> +
>>
>> I'm going to restate this later.  But I think you can include
>> "static" in the definition of your CCU_*_DEFINE() macros,
>> because all defined clocks will be static.  I believe they
>> can also get the const qualifier here.
> 
> I'm not sure about the const qualifier, but it's definitely okay to move
> static into the macros. I'll improve it in next version.

Unfortunately I think you're right.  You are including the clk_hw
structure in your initializations, and that can't be const.  You
could change this, but I won't suggest doing that now.  A lot
depends on this code being accepted, and I'd like not to delay
that.

> 
>>> +static CCU_PLL_DEFINE(pll1, pll1_rate_tbl,
>>> +		      APBS_PLL1_SWCR1, APBS_PLL1_SWCR3,
>>> +		      MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE);
>>> +static CCU_PLL_DEFINE(pll2, pll2_rate_tbl,
>>> +		      APBS_PLL2_SWCR1, APBS_PLL2_SWCR3,
>>> +		      MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE);
>>> +static CCU_PLL_DEFINE(pll3, pll3_rate_tbl,
>>> +		      APBS_PLL3_SWCR1, APBS_PLL2_SWCR3,
>>> +		      MPMU_POSR, POSR_PLL3_LOCK, CLK_SET_RATE_GATE);
>>> +
>>> +static CCU_GATE_FACTOR_DEFINE(pll1_d2, CCU_PARENT_HW(pll1),
>>> +			      APBS_PLL1_SWCR2,
>>> +			      BIT(1), 2, 1, 0);
>>
>> You seem to try to use multiple lines for these definitions
>> to possibly aid readability.  I think that's a good idea in
>> general.  But it isn't really consistent (I'll point out
>> a few examples below), and so it doesn't always work.  For
>> many (maybe all), the first line is consistent, but the rest
>> seem to vary a bit.
>>
>> These definitions are basically hardware definitions.  So
>> assuming you get them all correct initially, they'll never
>> change (for a given platform, like K1), and readability is
>> less of an issue.
>>
>> This time around I haven't scrutinized these in that much
>> detail.  But I think--once you're confident the definitions
>> are correct--you can simply format all of these clock
>> definitions to fit within 80 columns, and save a bunch of
>> lines in the source file.
> 
> Yes, sounds reasonable to me. I will do the reformatting if there's no
> objection against it.
> 
>>> +static CCU_GATE_FACTOR_DEFINE(pll1_d3, CCU_PARENT_HW(pll1),
>>> +			      APBS_PLL1_SWCR2,
>>> +			      BIT(2), 3, 1, 0);
>>
>> . . .
>>
>>> +static CCU_GATE_FACTOR_DEFINE(pll3_d8, CCU_PARENT_HW(pll3),
>>> +			      APBS_PLL3_SWCR2,
>>> +			      BIT(7), 8, 1, 0);
>>> +
>>
>> Here (below) you define these PLL "factor" clocks all on one line.
>>
>>> +static CCU_FACTOR_DEFINE(pll3_20, CCU_PARENT_HW(pll3_d8), 20, 1);
>>> +static CCU_FACTOR_DEFINE(pll3_40, CCU_PARENT_HW(pll3_d8), 10, 1);
>>> +static CCU_FACTOR_DEFINE(pll3_80, CCU_PARENT_HW(pll3_d8), 5, 1);
>>> +
>>> +/*	APBS clocks end		*/
>>
>> Use spaces rather than tabs, above and below (and throughout).
>> Or...  Use tabs consistently.  Pick one.
>>
>>> +
>>> +/*	MPMU clocks start	*/
>>> +static CCU_GATE_DEFINE(pll1_d8_307p2, CCU_PARENT_HW(pll1_d8),
>>> +		       MPMU_ACGR,
>>> +		       BIT(13), 0);
>>
>> But here you define the "factor" clock on two lines.  I'm sure
>> this is for line length.  I'd say just define them all according
>> to line length--make them fit within 80 columns, but otherwise
>> don't bother with extra lines for readability.
>>
>>> +static CCU_FACTOR_DEFINE(pll1_d32_76p8, CCU_PARENT_HW(pll1_d8_307p2),
>>> +			 4, 1);
>>
>> . . .
>>
>>> +
>>> +static const struct clk_parent_data apb_parents[] = {
>>> +	CCU_PARENT_HW(pll1_d96_25p6),
>>> +	CCU_PARENT_HW(pll1_d48_51p2),
>>> +	CCU_PARENT_HW(pll1_d96_25p6),
>>> +	CCU_PARENT_HW(pll1_d24_102p4),
>>> +};
>>
>> I think you should have a blank line after this definition (and
>> all other similar ones in this file).  In some cases you have
>> no blank line above this sort of definition, and you should.
> 
> These definitions are grouped by types and parents/children: similar
> clocks of the same type, like PWM ones, are kept together; possible
> parents and their children are kept together, too. IMHO it's more
> readable when looking through the clocks, but checkpatch.pl issues some
> suggestions about it as well. Anyway, I'm willing to make the changes.

Go ahead and leave out the blank lines I suggested.  It's
a simple coding style preference, and your grouping makes
sense.

> 
>>> +static CCU_MUX_DEFINE(apb_clk, apb_parents,
>>> +		      MPMU_APBCSCR,
>>> +		      0, 2,
>>> +		      0);
>>> +
>>> +static CCU_GATE_DEFINE(wdt_bus_clk, CCU_PARENT_HW(apb_clk),
>>> +		       MPMU_WDTPCR,
>>> +		       BIT(0),
>>> +		       0);
>>> +
>>> +static CCU_GATE_DEFINE(ripc_clk, CCU_PARENT_HW(apb_clk),
>>> +		       MPMU_RIPCCR,
>>> +		       0x1,
>>> +		       0);
>>> +/*	MPMU clocks end		*/
>>> +
>>> +/*	APBC clocks start	*/
>>> +static const struct clk_parent_data uart_clk_parents[] = {
>>> +	CCU_PARENT_HW(pll1_m3d128_57p6),
>>> +	CCU_PARENT_HW(slow_uart1_14p74),
>>> +	CCU_PARENT_HW(slow_uart2_48),
>>> +};
>>
>> Here you're putting the control register and flags field on
>> separate lines.  I'd say just join them.
> 
> I'll reformat all clocks, only following the 80-columns rule, so similar
> separation shouldn't be in next version.
> 
>>> +static CCU_MUX_GATE_DEFINE(uart0_clk, uart_clk_parents,
>>> +			   APBC_UART1_CLK_RST,
>>> +			   4, 3, BIT(1),
>>> +			   CLK_IS_CRITICAL);
>>
>> . . .
>>
>>> +struct spacemit_ccu_clk {
>>> +	int id;
>>> +	struct clk_hw *hw;
>>> +};
>>> +
>>
>> The following array defines the clocks under what is matched
>> as "spacemit,k1-pll".  I know it's the "apbs" (APB Spare)
>> memory region, but I think it would be better to have the
>> name here match the compatible string.  Add a comment if
>> you want to make sure people understand the "PLL<->APBS"
>> relationship.
> 
> Thanks, will take it.
> 
>>> +static struct spacemit_ccu_clk k1_ccu_apbs_clks[] = {
>>> +	{ CLK_PLL1,		&pll1.common.hw },
>>> +	{ CLK_PLL2,		&pll2.common.hw },
>>> +	{ CLK_PLL3,		&pll3.common.hw },
>>
>> . . .
>>
>>> +static int spacemit_ccu_register(struct device *dev,
>>> +				 struct regmap *regmap, struct regmap *lock_regmap,
>>> +				 const struct spacemit_ccu_clk *clks)
>>> +{
>>> +	const struct spacemit_ccu_clk *clk;
>>> +	int i, ret, max_id = 0;
>>> +
>>> +	for (clk = clks; clk->hw; clk++)
>>> +		max_id = max(max_id, clk->id);
>>> +
>>> +	struct clk_hw_onecell_data *clk_data;
>>
>> The definition of clk_data belongs at the top of the function.
> 
> Oops, I miss it. Will fix the style.
> 
>>> +
>>> +	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, max_id + 1), GFP_KERNEL);
>>> +	if (!clk_data)
>>> +		return -ENOMEM;
>>> +
>>> +	for (i = 0; i <= max_id; i++)
>>> +		clk_data->hws[i] = ERR_PTR(-ENOENT);
>>> +
>>> +	for (clk = clks; clk->hw; clk++) {
>>> +		struct ccu_common *common = hw_to_ccu_common(clk->hw);
>>> +		const char *name = clk->hw->init->name;
>>> +
>>> +		common->regmap		= regmap;
>>> +		common->lock_regmap	= lock_regmap;
>>> +
>>> +		ret = devm_clk_hw_register(dev, clk->hw);
>>> +		if (ret) {
>>> +			dev_err(dev, "Cannot register clock %d - %s\n",
>>> +				i, name);
>>> +			return ret;
>>> +		}
>>> +
>>> +		clk_data->hws[clk->id] = clk->hw;
>>> +	}
>>> +
>>> +	clk_data->num = max_id + 1;
>>> +
>>> +	return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
>>> +}
>>> +
>>> +static int k1_ccu_probe(struct platform_device *pdev)
>>> +{
>>> +	struct regmap *base_regmap, *lock_regmap = NULL;
>>> +	struct device *dev = &pdev->dev;
>>> +	int ret;
>>> +
>>> +	base_regmap = device_node_to_regmap(dev->of_node);
>>> +	if (IS_ERR(base_regmap))
>>> +		return dev_err_probe(dev, PTR_ERR(base_regmap),
>>> +				     "failed to get regmap\n");
>>> +
>>
>> You should add a comment here explaining why there is a special
>> additional regmap for the PLL clock type (because it requires a
>> lock that's found in that that device node's memory region).
> 
> I guess there's already some explanation in the dt-bindings, but I'm
> willing to add some more :)

Yes, you should explain it in the code.  It doesn't have to be
a complete explanation, but people reading the code aren't
always going to be looking at the bindings.  It's nice to know
why this is different from the others.

> 
> ...
> 
>>> diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
>>> new file mode 100644
>>> index 000000000000..494cde96fe3c
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_common.h
>>> @@ -0,0 +1,47 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-only */
>>> +/*
>>> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
>>> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
>>> + */
>>> +
>>> +#ifndef _CCU_COMMON_H_
>>> +#define _CCU_COMMON_H_
>>> +
>>> +#include <linux/regmap.h>
>>> +
>>
>> I'm not going to suggest it at this point, but it might
>> have worked out more nicely if you defined a top-level CCU
>> structure that contained a union of structs, one for each
>> type of clock (PLL, DDN, mix).
>>
>>> +struct ccu_common {
>>> +	struct regmap *regmap;
>>> +	struct regmap *lock_regmap;
>>
>> The lock_regmap is only used for PLL type clocks, right?
>> So it could be included in the PLL struct within the union
>> below?
> 
> This makes sense to me.

It might make sense but in part my suggestion makes more
sense when taken with the comment I had above (about having
a CCU structure with a union rather than separate per-type
structures).  I think that would be better, but again I
don't want you to have to do all that work if it means
delaying getting your code accepted.

So move it to the union if that works, but for now it's
fine the way it is.

>>> +
>>> +	union {
>>> +		/* For DDN and MIX */
>>> +		struct {
>>> +			u32 reg_ctrl;
>>> +			u32 reg_fc;
>>> +			u32 fc;
>>
>> The fc field is a bit mask, and with a single bit set.
>>
>> Other fields you define use the convention "mask" in the name
>> to indicate it is a bit mask.  So I suggest you name "fc" to
>> be "mask_fc" or "fc_mask" (but in the latter case, I'd rename
>> the registers to be "fc_reg" and "ctrl_reg"--you decide).
> 
> I prefer the former.

Your choice.  My main point was about including "mask" in
the name.

> 
>> Since fc is a (nonzero) mask, you could use a zero fc value
>> to indicate that reg_fc is the same as reg_ctrl.
> 
> I'm not sure about what you mean and what the change could improve: even
> in case that reg_fc is the same as reg_ctrl, we still require a field to
> store the mask for FC bit.
> 
> Knowing reg_ctrl is just reg_fc doesn't help much: we could directly
> assign reg_ctrl to reg_fc in our macros and avoid extra logic in
> ccu_mix.c.

Yes, what you have is fine.

> 
>>> +		};
>>> +
>>> +		/* For PLL */
>>> +		struct {
>>> +			u32 reg_swcr1;
>>> +			u32 reg_swcr2;
>>
>> The reg_swcr2 is no longer used (now that you've dropped it
>> from the ccu_pll_rate_tbl structure).
> 
> Will drop it. I think it was missed during cleanup.
> 
>>> +			u32 reg_swcr3;
>>
>> You define PLL_SWCR3_EN in "ccu_pll.c" to have value BIT(31).
>> that's good.  But you should define its inverse, to define
>> which bits in the reg_swcr3 field are the valid "magic" part.
>> In both cases, I would define them here in this file, where
>> the structure type is defined (not in "ccu_pll.c").
>> #define SPACEMIT_PLL_SWCR3_EN	(u32)BIT(31)
>> #define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)
>>
>>> +		};
>>> +	};
>>> +
>>> +	struct clk_hw hw;
>>> +};
>>> +
>>> +static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
>>> +{
>>> +	return container_of(hw, struct ccu_common, hw);
>>> +}
>>> +
>>
>> This isn't a huge deal, but I think since your functions here
>> are named ccu_*(), the first argument should be the CCU (or
>> ccu_common) structure.
>>
>>> +#define ccu_read(reg, c, val)	regmap_read((c)->regmap, (c)->reg_##reg, val)
>>
>> I commented before that you're not checking return values for
>> regmap_read() and regmap_write().  You said you don't expect
>> MMIO accesses to ever fail.
>>
>> If we assume that, I'd like to see ccu_read() defined differently,
>> so that it returns a value (rather than taking the assigned-to
>> address as an argument).  To me, it makes it much easier to
>> understand in places it's used, making it more obvious an
>> assignment is getting made.
> 
> Thanks for the suggesion,
> 
>> static inline u32 _ccu_read(struct ccu_common *common, u32 offset)
>> {
>> 	u32 val;
>>
>> 	(void)regmap_read(common->regmap, offset>reg_##reg, &val);
>>
>> 	return val;
>> }
>> #define ccu_read(reg, c)	_ccu_read((c), (c)->reg_##reg)
>>
>> 	val = ccu_read(ctrl, &ddn->common);
>>
> 
> And we may avoid the extra inline function with GNU compound statement,
> just like how read_poll_timeout_atomic() is defined.

Honestly, I'd really just rather see you calling regmap_read()
in your code.  And... check its return value.

But you do what you like.

> 
>>> +#define ccu_update(reg, c, mask, val) \
>>> +	regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)7
>>> +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \
>>> +	regmap_read_poll_timeout_atomic((c)->regmap, (c)->reg_##reg,	\
>>> +					tmp, cond, sleep, timeout)
>>> +
>>> +#endif /* _CCU_COMMON_H_ */
>>> diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c
>>> new file mode 100644
>>> index 000000000000..ee187687d0c4
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_ddn.c
>>> @@ -0,0 +1,80 @@
> 
> ...
> 
>>> +static unsigned long clk_ddn_calc_best_rate(struct ccu_ddn *ddn,
>>> +					    unsigned long rate, unsigned long prate,
>>> +					    unsigned long *num, unsigned long *den)
>>> +{
>>> +	rational_best_approximation(rate, prate / 2,
>>> +				    ddn->den_mask, ddn->num_mask,
>>> +				    den, num);
>>
>> Using rational_best_approximation() is excellent.  However I
>> think you have a bug, and I don't think the exact way you're
>> using it is clear (and might be wrong).
>>
>> The bug is that the third and fourth arguments are the maximum
>> numerator and denominator, respectively.  You are passing mask
>> values, which in some sense represent the maximums.  However,
>> your masks are not always in the low-order bits.  Here is one
>> example:
>>
>> static CCU_DDN_DEFINE(slow_uart1_14p74, pll1_d16_153p6,
>>                        MPMU_SUCCR,
>>                        GENMASK(28, 16), 16, GENMASK(12, 0), 0,
>>                        0);
>>
>> The "_num_mask" argument to this macro is 0x1fff0000, and the
>> "_den_mask" is 0x00000fff.  The latter value (which gets passed
>> as the max_numerator argument to rational_best_approximation())
>> is fine, but the former is not.  So you need to shift both masks
>> right by their corresponding shift value.
> 
> Thanks for finding it! I forget to change the function when redefining
> struct ccu_ddn.
> 
>> Beyond that bug, rational_best_approximation() wants its first
>> two arguments to define the desired rate (as a fraction).
> 
> I don't think it's the way that rational_best_approximation() works.
> The first two arguments define the desired fraction, not the rate.
> 
>> So the desired rate should be the actual desired rate divided by 1
>> (rather than being divided by the half the parent rate).  So
>> this too might be a bug.

OK I took another look at this.  And I looked at the first commit
that used this function to understand how to use it:
   534fca068ec80 imx: serial: use rational library function

You want to know what are the best available numerator and
denominator values to use (which fit into your register fields).
These should be as close as possible to the fraction you're after.

     Fout = Fin * num / denom

Fin is the parent rate (always divided by two in this case),
or "prate / 2".  Fout is the desired rate, or "rate".  You might
get a better result if you express the "/ 2" in the parent rate
as "* 2" in the desired rate.

     num_max = ddn->num_mask >> __ffs(ddn->num_mask);
     den_max = ddn->den_mask >> __ffs(ddn->den_mask);
     rational_best_approximation(rate * 2, prate,
				num_max, den_max, &num, &den)

In other words, your original code was more or less right,
with the numerator and denominator reversed.

> 
>  From the comment in its implementation,
> 
>    * calculate best rational approximation for a given fraction
>    * taking into account restricted register size
> 
> And we have
> 
>              numerator       Fin
>        2 * ------------- = -------
>             denominator      Fout
> 
> divide two on both sides,
> 
>              numerator       Fin / 2
>            ------------- = -----------
>             denominator       Fout
> 
> So we could take rational_best_approximation() to calculate the best
> approximated num and den, where give_numerator = Fin / 2 = prate / 2,
> given_denominator = Fout = rate, best_numerator = numerator = &num and
> best_denominator = denominator = &den. My code swaps den and num when
> passing them to rational_best_approximation(), but this doesn't affect
> the result and I'll fix it in next version.

OK, maybe this was why I was confused?  Probably not.

>> Maybe I'm misinterpreting this, but the bottom line is I'm
>> pretty certain this is not producing the correct numerator
>> and denominator that would produce the closest rate supported
>> by the hardware.

Looks like I was wrong.

>>> +	return prate / 2 * *den / *num;
>>
>> The above matches your explanation of how the output rate
>> is computed based on the "denominator" and "numerator".
>> You could encapsulate simply that in a function, given
>> tnat you used it here and another time below.
>>
>> You might get slightly better precision by deferring the
>> divide-by-2.
> 
> Thanks, will take them.
> 
> ...
> 
>>> diff --git a/drivers/clk/spacemit/ccu_ddn.h b/drivers/clk/spacemit/ccu_ddn.h
>>> new file mode 100644
>>> index 000000000000..3746d084e1e7
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_ddn.h
>>> @@ -0,0 +1,48 @@
>>> +/* SPDX-License-Identifier: GPL-2.0-only */
>>> +/*
>>> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
>>> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
>>> + */
>>> +
>>> +#ifndef _CCU_DDN_H_
>>> +#define _CCU_DDN_H_
>>> +
>>> +#include <linux/clk-provider.h>
>>> +
>>> +#include "ccu_common.h"
>>> +
>>> +struct ccu_ddn {
>>> +	struct ccu_common common;
>>> +	unsigned int num_mask;
>>> +	unsigned int num_shift;
>>> +	unsigned int den_mask;
>>> +	unsigned int den_shift;
>>> +};
>>> +
>>> +#define CCU_DDN_INIT(_name, _parent, _flags) \
>>> +	CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
>>> +
>>
>> In CCU_DDN_DEFINE(), both the numerator and denominator masks
>> consist of a contiguous block of 1 bits, with zero or more 0
>> bits to their left and right.  They are always constant (both
>> times it's used...).
>>
>> For that reason, I'd like you to drop the _num_shift and _den_shift
>> arguments from this macro.  Instead, use __ffs(_num_mask) and
>> __ffs(_den_mask) when assigning the num_shift and den_shift field
>> values.
> 
> Thanks, it's reasonable to me.
> 
> ...
> 
>>> diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c
>>> new file mode 100644
>>> index 000000000000..a5c13000e062
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_mix.c
>>> @@ -0,0 +1,284 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Spacemit clock type mix(div/mux/gate/factor)
>>> + *
>>> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
>>> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
>>> + */
>>> +
>>> +#include <linux/clk-provider.h>
>>> +
>>> +#include "ccu_mix.h"
>>> +
>>
>> I think the name of this constant should include "FC", and a
>> comment should indicate it is a number of microseconds. "FC"
>> stands for "frequency change"; I think you should have a comment
>> explaining exactly what that means (somewhere).
> 
> This macro was also used for a polling after gate is enabled, but I've
> confirmed with the vendor that it's unnecessary. Seems I forgot to
> rename it.
> 
> I prefer to give it a self-explained name, like MIX_FC_TIMEOUT_US, and
> explain the FC stuff above ccu_mix_trigger_fc() in comments.

Sounds good.

> 
>>> +#define MIX_TIMEOUT	10000
>>> +
>>> +static void ccu_gate_disable(struct clk_hw *hw)
>>> +{
>>> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
>>> +	struct ccu_common *common = &mix->common;
>>> +
>>> +	ccu_update(ctrl, common, mix->gate.mask, 0);
>>> +}
>>
>> Maybe drop the second local variable and do:
>>
>> 	ccu_update(ctrl, &mix->common, mix->gate.mask, 0);
>>
>> And this can be done several times below.
> 
> Makes sense to me.
> 
>>> +
>>> +static int ccu_gate_enable(struct clk_hw *hw)
>>> +{
>>> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
>>> +	struct ccu_common *common = &mix->common;
>>> +	struct ccu_gate_config *gate = &mix->gate;
>>> +
>>> +	ccu_update(ctrl, common, gate->mask, gate->mask);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int ccu_gate_is_enabled(struct clk_hw *hw)
>>> +{
>>> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
>>> +	struct ccu_common *common = &mix->common;
>>> +	u32 tmp;
>>> +
>>> +	ccu_read(ctrl, common, &tmp);
>>> +
>>
>> The next line won't necessarily work.  The gate mask
>> value in a few cases contains more than one set bit
>> (rtc_clk is an exmaple).  Therefore this will return
>> true even if just one of those bits is set.  This is
>> most likely a bug.
>>
>> This needs to be:
>>
>> 	return (tmp & mix->gete.mask) == mix->gate.mask;
> 
> Oops, thanks, I didn't expect the case.
> 
>>> +	return !!(tmp & mix->gate.mask);
>>> +}
>>> +
> 
> ...
> 
>>> +static unsigned long
>>> +ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
>>> +		       struct clk_hw **best_parent,
>>> +		       unsigned long *best_parent_rate,
>>> +		       u32 *div_val)
>>> +{
>>> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
>>> +	unsigned int parent_num = clk_hw_get_num_parents(hw);
>>> +	struct ccu_div_config *div = &mix->div;
>>> +	u32 div_max = 1 << div->width;
>>> +	unsigned long best_rate = 0;
>>> +
>>> +	for (int i = 0; i < parent_num; i++) {
>>> +		struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
>>> +		unsigned long parent_rate;
>>> +
>>> +		if (!parent)
>>> +			continue;
>>> +
>>> +		parent_rate = clk_hw_get_rate(parent);
>>> +
>>> +		for (int j = 1; j <= div_max; j++) {
>>> +			unsigned long tmp = DIV_ROUND_UP_ULL(parent_rate, j);
>>
>> I might have asked this before.  Why round up?  Why not
>> round closest?
>>
> 
> Rounding to closest should be correct here, as determine_rate() is
> expected to return the closest rate, thanks for pointing out this.
> 
>>> +
>>> +			if (abs(tmp - rate) < abs(best_rate - rate)) {
>>> +				best_rate = tmp;
>>> +
>>> +				if (div_val)
>>> +					*div_val = j - 1;
>>> +
>>> +				if (best_parent) {
>>> +					*best_parent      = parent;
>>> +					*best_parent_rate = parent_rate;
>>> +				}
>>> +			}
>>> +		}
>>> +	}
>>> +
>>> +	return best_rate;
>>> +}
> 
> ...
> 
>>> +static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
>>> +{
>>> +	struct ccu_mix *mix = hw_to_ccu_mix(hw);
>>> +	struct ccu_common *common = &mix->common;
>>> +	struct ccu_mux_config *mux = &mix->mux;
>>> +	int ret = 0;
>>> +	u32 mask;
>>> +
>>
>> I'm not sure it will ever happen, but if index is out of range
>> this most likely won't do the right thing.
>>
>> 	if (WARN_ON(index >= 1 << mux->width))
>> 		return -EINVAL;
> 
> I don't think such case is possible, since all indexes passed in
> set_parent() callback are calculated by clk_parent_index, which always
> produces a valid index or error code if we correctly set num_parents.

OK.

> 
>>> +	mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
>>> +
>>> +	ccu_update(ctrl, common, mask, index << mux->shift);
>>> +
>>> +	if (common->reg_fc)
>>> +		ret = ccu_mix_trigger_fc(hw);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>
>> I think your naming for these should follow a pattern, for
>> example:
>>    gate then (factor or div) then mux then fc
> 
> Okay, mux then factor/div then gate then fc sounds better to me, since
> it reflects the order of these hardwares as shown in the clock diagram.

Sounds great.

> 
> I guess this should apply for CCU_*_DEFINE() macros as well, right?

Consistency is pretty much always your friend.

> 
>>> +const struct clk_ops spacemit_ccu_gate_ops = {
>>> +	.disable	= ccu_gate_disable,
>>> +	.enable		= ccu_gate_enable,
>>> +	.is_enabled	= ccu_gate_is_enabled,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_factor_ops = {
>>> +	.round_rate	= ccu_factor_round_rate,
>>> +	.recalc_rate	= ccu_factor_recalc_rate,
>>> +	.set_rate	= ccu_factor_set_rate,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_mux_ops = {
>>> +	.determine_rate = ccu_mix_determine_rate,
>>> +	.get_parent	= ccu_mux_get_parent,
>>> +	.set_parent	= ccu_mux_set_parent,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_div_ops = {
>>> +	.determine_rate = ccu_mix_determine_rate,
>>> +	.recalc_rate	= ccu_div_recalc_rate,
>>> +	.set_rate	= ccu_mix_set_rate,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_gate_factor_ops = {
>>> +	.disable	= ccu_gate_disable,
>>> +	.enable		= ccu_gate_enable,
>>> +	.is_enabled	= ccu_gate_is_enabled,
>>> +
>>> +	.round_rate	= ccu_factor_round_rate,
>>> +	.recalc_rate	= ccu_factor_recalc_rate,
>>> +	.set_rate	= ccu_factor_set_rate,
>>> +};
>>> +
>>
>> If you follow the pattern I said above, this would be
>> spacemit_ccu_gate_mux_ops().
>>
>> (And so on.)
>>
>>> +const struct clk_ops spacemit_ccu_mux_gate_ops = {
>>> +	.disable	= ccu_gate_disable,
>>> +	.enable		= ccu_gate_enable,
>>> +	.is_enabled	= ccu_gate_is_enabled,
>>> +
>>> +	.determine_rate = ccu_mix_determine_rate,
>>> +	.get_parent	= ccu_mux_get_parent,
>>> +	.set_parent	= ccu_mux_set_parent,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_div_gate_ops = {
>>> +	.disable	= ccu_gate_disable,
>>> +	.enable		= ccu_gate_enable,
>>> +	.is_enabled	= ccu_gate_is_enabled,
>>> +
>>> +	.determine_rate = ccu_mix_determine_rate,
>>> +	.recalc_rate	= ccu_div_recalc_rate,
>>> +	.set_rate	= ccu_mix_set_rate,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_div_mux_gate_ops = {
>>> +	.disable	= ccu_gate_disable,
>>> +	.enable		= ccu_gate_enable,
>>> +	.is_enabled	= ccu_gate_is_enabled,
>>> +
>>> +	.get_parent	= ccu_mux_get_parent,
>>> +	.set_parent	= ccu_mux_set_parent,
>>> +
>>> +	.determine_rate = ccu_mix_determine_rate,
>>> +	.recalc_rate	= ccu_div_recalc_rate,
>>> +	.set_rate	= ccu_mix_set_rate,
>>> +};
>>> +
>>> +const struct clk_ops spacemit_ccu_div_mux_ops = {
>>> +	.get_parent	= ccu_mux_get_parent,
>>> +	.set_parent	= ccu_mux_set_parent,
>>> +
>>> +	.determine_rate = ccu_mix_determine_rate,
>>> +	.recalc_rate	= ccu_div_recalc_rate,
>>> +	.set_rate	= ccu_mix_set_rate,
>>> +};
>>> diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h
>>> new file mode 100644
>>> index 000000000000..a3aa292d073d
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_mix.h
> 
> ...
> 
>> I think you should define these next two with their types.
>>
>>> +#define CCU_PARENT_HW(_parent)		{ .hw = &_parent.common.hw }
>>> +#define CCU_PARENT_NAME(_name)		{ .fw_name = #_name }
>>> +
> 
> It's not necessary since there're enough context for the compiler in all
> use cases. It's okay for me if you consider it's better to introduce
> some extra clearness,

In this case I think it wasn't obvious to me while reading what
the type of the initialized struct was, and I needed to go look
at where it was used to understand.  If the compiler doesn't
complain it's fine, don't change it.

>> #define CCU_PARENT_HW(_parent) \
>> 	(struct clk_parent_data){ .hw = &_parent.common.hw }
>>
>>> +#define CCU_MIX_INITHW(_name, _parent, _ops, _flags)			\
>>> +	(&(struct clk_init_data) {					\
>>
>> I'm pretty sure the left parenthesis above and its matching
>> right parenthesis below are not needed.
> 
> CCU_MIX_INIT_HW is defined just like CLK_HW_INIT_HW(), which includes
> these parenthesis. I think they're for guarding usage of macros and not
> needed for our definition. Will remove them.
> 
>>> +		.flags		= _flags,				\
>>> +		.name		= #_name,				\
>>
>> If the type is defined above, you don't need to cast it here.
> 
> It's not a cast, it's a compound literal[1] introduced in C99, which
> cannot be dropped even if we define the type above.
> 
>> Also the curly braces around _parent look strange to me.  The
>> parent_data field is a pointer, and ideally there should be
>> no need to use a type cast here.
> 
> parent_data is a scalar pointer instead of a compound type, thus we
> cannot define its value by simply putting a pair of braces. We have
> two options here,
> 
> 1. Specify the type in CCU_PARENT_{HW,NAME}, make it a compound literal
>     and taking its address in CCU_MIX_INITHW()
> 
> 	#define CCU_PARENT_HW(_parent) \
> 		(struct clk_parent_data){ .hw = &_parent.common.hw }
> 
> 	/* in CCU_MIX_INITHW() */
> 		.parent_data = &_parent,
> 
> 2. Create an anonymous array by compound literal in CCU_MIX_INITHW() and
>     take its address. Members of the array are compound types, thus it's
>     okay to omit the type in CCU_PARENT_{HW,NAME}.
> 
> Option 2 is our current solution and also what standard ccf macros
> (CLK_HW_INIT_HW and etc.) look like. I cannot decide which is better.

Now I see, you're relying on anonymous objects like the common clock
framework does.  And it sounds like my general view as I was reading
the code was more like option 1.

I think either one is fine, which means what you did is OK as-is.

The layers of macros for initializing these things make it a little
harder to follow.

>>> +		.parent_data	= (const struct clk_parent_data[])	\
>>> +					{ _parent },			\
>>> +		.num_parents	= 1,					\
>>> +		.ops		= &_ops,				\
>>> +	})
>>> +
>>> +#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags)		\
>>> +	CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
>>> +
>>
>> Many/most of the other CCU_*_DEFINE() macros use the name
>> _ctrl_reg for the register.  I suggest you use that here
>> (and everywhere, for consistency).
> 
> I will take it.
> 
>>> +#define CCU_GATE_DEFINE(_name, _parent, _reg, _gate_mask, _flags)		\
>>
>> static const struct ccu_mix _name = { \
>>
>>> +struct ccu_mix _name = {							\
>>> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
>>> +	.common	= {								\
>>> +		.reg_ctrl	= _reg,						\
>>
>> Why do you have a tab before the equal sign here?  (But not
>> elsewhere?)  Whatever you decide, be consistent.
> 
> It's just for saving some lines: CCU_MIX_INITHW() seems too long and
> causes early line breaks. Is it acceptable to move the whole assignement
> into CCU_MIX_INITHW() macro? This should look better.

I'm not sure, my point was about using white space consistently.

> 
>>> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
>>> +					  spacemit_ccu_gate_ops, _flags),	\
>>> +	}									\
>>> +}
>>> +
>>> +#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul)				\
>>
>> static const struct ccu_mix _name = { \
> 
> As discussed in ccu-k1.c, I'll take the change.
> 
>>> +struct ccu_mix _name = {							\
>>> +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
>>> +	.common = {								\
>>> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
>>> +					  spacemit_ccu_factor_ops, 0),		\
>>> +	}									\
>>> +}
> 
> ...
> 
>>> +#define CCU_GATE_FACTOR_DEFINE(_name, _parent,					\
>>> +			       _reg,						\
>>> +			       _gate_mask,					\
>>> +			       _div, _mul,					\
>>> +			       _flags)						\
>>
>> static const struct ccu_mix _name = { \
>>
>> I believe the value of _flags is always zero, so that argument
>> could be eliminated (it can be added back in the future if it's
>> ever needed).
> 
> Makes sense to me, I'm willing to take it.
> 
>> There are only two cases where the value of _mul is different
>> from 1.  You could define a slightly different macro for those
>> cases, and for this, remove the multiplier argument.
> 
> I don't think it's worth introducing an extra macro for saving only two
> or three types, we already have a lot of them.

OK.

>>> +struct ccu_mix _name = {							\
>>> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
>>> +	.factor	= CCU_FACTOR_INIT(_div, _mul),					\
>>> +	.common = {								\
>>> +		.reg_ctrl	= _reg,						\
>>> +		.hw.init = CCU_MIX_INITHW(_name, _parent,			\
>>> +					  spacemit_ccu_gate_factor_ops, _flags)	\
>>> +	}									\
>>> +}
>>> +
> 
> ...
> 
>> I think I might have said this elsewhere;  use _fc_mask
>> (or something similar) to define what you call _fc here.
> 
> Yes, in ccu_common.h. I'll take it.
> 
>>> +#define CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents,			\
>>> +					 _reg_ctrl, _reg_fc,			\
>>> +					 _mshift, _mwidth,			\
>>> +					 _fc,					\
>>> +					 _muxshift, _muxwidth,			\
>>> +					 _gate_mask,				\
>>> +					 _flags)				\
>>
>> static const struct ccu_mix _name = { \
>>
>>> +struct ccu_mix _name = {							\
>>> +	.gate	= CCU_GATE_INIT(_gate_mask),					\
>>> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
>>> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
>>> +	.common = {								\
>>> +		.reg_ctrl	= _reg_ctrl,					\
>>> +		.reg_fc		= _reg_fc,					\
>>> +		.fc		= _fc,						\
>>
>> In all cases, these FC masks have a single bit set; this is
>> something I think is worth mentioning in a comment.
> 
> I think ccu_mix_trigger_fc() seems a good place for it: we could explain
> the FC machanism and the special nature of the fc (fc_mask) field
> together.
> 
>>> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
>>> +						  spacemit_ccu_div_mux_gate_ops,\
>>> +						  _flags),			\
>>> +	},									\
>>> +}
>>> +
> 
> ...
> 
>> static const struct ccu_mix _name = { \
>>
>>> +struct ccu_mix _name = {							\
>>> +	.div	= CCU_DIV_INIT(_mshift, _mwidth),				\
>>> +	.mux	= CCU_MUX_INIT(_muxshift, _muxwidth),				\
>>> +	.common = {								\
>>> +		.reg_ctrl	= _reg_ctrl,					\
>>> +		.reg_fc		= _reg_ctrl,					\
>>
>> I suggested elsewhere that you could use a non-zero FC mask
>> to indicate the FC register is the same as the reg_ctrl
>> register.  (Despite my repeated suggestion, what you're
>> doing already is fine...)
> 
> Please refer to my question about the non-zero FC mask in ccu_common.h.

You can ignore my comment.

>>> +		.fc		= _fc,						\
>>> +		.hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents,		\
>>> +						  spacemit_ccu_div_mux_ops,	\
>>> +						  _flags),			\
>>> +	},									\
>>> +}
> 
> ...
> 
>> I think you can define each ops variable on its own line.
> 
> I will take it, which makes the code cleaner.
> 
>>> +extern const struct clk_ops spacemit_ccu_gate_ops, spacemit_ccu_factor_ops;
>>> +extern const struct clk_ops spacemit_ccu_mux_ops, spacemit_ccu_div_ops;
>>> +
>>> +extern const struct clk_ops spacemit_ccu_gate_factor_ops;
>>> +extern const struct clk_ops spacemit_ccu_div_gate_ops;
>>> +extern const struct clk_ops spacemit_ccu_mux_gate_ops;
>>> +extern const struct clk_ops spacemit_ccu_div_mux_ops;
>>> +
>>> +extern const struct clk_ops spacemit_ccu_div_mux_gate_ops;
>>> +#endif /* _CCU_DIV_H_ */
>>> diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
>>> new file mode 100644
>>> index 000000000000..9df2149f6c98
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_pll.c
>>> @@ -0,0 +1,146 @@
>>> +// SPDX-License-Identifier: GPL-2.0-only
>>> +/*
>>> + * Spacemit clock type pll
>>> + *
>>> + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
>>> + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
>>> + */
>>> +
>>> +#include <linux/clk-provider.h>
>>> +#include <linux/regmap.h>
>>> +
>>> +#include "ccu_common.h"
>>> +#include "ccu_pll.h"
>>> +
>>
>> Name this just PLL_TIMEOUT, or maybe CCU_PLL_TIMEOUT, and
>> indicate in a comment that it is a period in microseconds.
> 
> Same for the FC timeout case, I'd like to append a "_US" suffix to
> indicate its unit.
> 
> ...
> 
>>> +/* frequency unit Mhz, return pll vco freq */
>>> +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
>>> +{
>>> +	const struct ccu_pll_rate_tbl *pll_rate_table;
>>> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
>>> +	struct ccu_common *common = &p->common;
>>> +	u32 swcr1, swcr3, size;
>>> +	int i;
>>> +
>>> +	ccu_read(swcr1, common, &swcr1);
>>> +	ccu_read(swcr3, common, &swcr3);
>>
>> You are masking off the EN bit, but you should really be
>> using a mask defining which bits are valid instead.  As
>> I said earlier:
>>
>> #define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)
>>
>>> +	swcr3 &= ~PLL_SWCR3_EN;
>>
>> 	swcr3 &= SPACEMIT_PLL_SWCR3_MASK;
>>> +
>>> +	pll_rate_table = p->pll.rate_tbl;
>>> +	size = p->pll.tbl_size;
>>> +
>>> +	for (i = 0; i < size; i++) {
>>> +		if (pll_rate_table[i].swcr1 == swcr1 &&
>>> +		    pll_rate_table[i].swcr3 == swcr3)
>>> +			return pll_rate_table[i].rate;
>>> +	}
>>> +
>>
>> I have a general question here.  Once you set one of these
>> clock rates, it will always use one of the rates defined
>> in the table.
>>
>> But what about initially?  Could the hardware start in a
>> state that is not defined by this code?  Do you *set* the
>> rate initially?  Should you (at least the first time the
>> clock is prepared/enabled)?
> 
> Thanks, I've also seen your later report. Here we may support
> clk_ops.init and reinitialize the PLL if it's not in a good state.
> 
> Could you please provide a possible reproducing scenario for me to test
> against the PLL problem?

What I can tell you is that I see the warning, perhaps when
I'm using the clock.  I'll try to narrow down a test case but
right now I don't have one.

[    0.145906] WARNING: CPU: 0 PID: 1 at 
drivers/clk/spacemit/ccu_pll.c:51 ccu_pll_recalc_rate+0x76/0x9a

I added code to report the swcr1 and swcr3 values but I don't
have those right now.

> 
>>> +	WARN_ON_ONCE(1);
>>
>> Maybe WARN_ONCE(true, "msg");
>>
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int ccu_pll_enable(struct clk_hw *hw)
>>> +{
>>> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
>>> +	struct ccu_common *common = &p->common;
>>> +	unsigned int tmp;
>>> +	int ret;
>>> +
>>
>> Get rid of ret (see below).
>>
>> Will clk_ops->enable() ever be called when it's already
>> enabled?  (If it won't, this isn't needed.  If it will,
>> this checks the hardware, which is good.)
> 
> CCF holds a refcounter of clock consumers, so we could drop the check.
> 
>>> +	if (ccu_pll_is_enabled(hw))
>>> +		return 0;
>>> +
>>> +	ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN);
>>> +
>>> +	/* check lock status */
>>> +	ret = regmap_read_poll_timeout_atomic(common->lock_regmap,
>>> +					      p->pll.reg_lock,
>>> +					      tmp,
>>> +					      tmp & p->pll.lock_enable_bit,
>>> +					      5, PLL_DELAY_TIME);
>>
>> Just:
>>
>> 	return regmap_read_poll_timeout_atomic(...);
>>
>> I note that you call this here, but you hide the call
>> to regmap_read_poll_timeout_atomic() behind the macro
>> ccu_poll().  And ccu_poll() (used for the FC bit) is
>> also only called once.
>>
>> I suggest you get rid of regmap_poll() and just open-code
>> it.
>>
>> (You use ccu_read() and ccu_update() numerous times, so
>> your "saving some characters" is justified.)
> 
> Makes sense to me, will take it.
> 
>>> +
>>> +	return ret;
>>> +}
> 
> ...
> 
>>> +/*
>>> + * PLLs must be gated before changing rate, which is ensured by
>>> + * flag CLK_SET_RATE_GATE.
>>> + */
>>> +static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
>>> +			    unsigned long parent_rate)
>>> +{
>>> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
>>> +	struct ccu_common *common = &p->common;
>>> +	struct ccu_pll_config *params = &p->pll;
>>> +	const struct ccu_pll_rate_tbl *entry = NULL;
>>> +	int i;
>>> +
>>> +	for (i = 0; i < params->tbl_size; i++) {
>>> +		if (rate == params->rate_tbl[i].rate) {
>>> +			entry = &params->rate_tbl[i];
>>> +			break;
>>> +		}
>>> +	}
>>> +
>>> +	if (WARN_ON_ONCE(!entry))
>>> +		return -EINVAL;
>>> +
>>
>> The next line contains a bug.  The third argument defines
>> a mask of which bits get updated.  Therefore, any zero bits
>> in that mask will *not* get updated in the swcr1 register
>> even if they should be.
>>
>> Example:
>> Old SWCR1:	0x01234567
>> New SWCR1:	0x00112233
>>
>> Updated:	0x01336777
>> Want:		0x00112233
>>
>> You should either define ccu_write() (my preference), or
>> you you should call:
> 
> Or maybe we could just call regmap_write directly here, it's the only
> place we require unprotected writes (PLL registers aren't shared) and
> it's not worth introducing the macro again.

Yes!

> 
>> 	ccu_update(swcr1, common, ~0, entry->swcr1);
>>
>>
>>> +	ccu_update(swcr1, common, entry->swcr1, entry->swcr1);
>>
>> You should use SPACEMIT_PLL_SWCR3_MASK below.  No
>> cast should be needed (define the symbol with one).
>>
>>> +	ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3);
>>> +
>>> +	return 0;
>>> +}
> 
> ...
> 
>>> +static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
>>> +			       unsigned long *prate)
>>> +{
>>> +	struct ccu_pll *p = hw_to_ccu_pll(hw);
>>> +	struct ccu_pll_config *params = &p->pll;
>>> +	unsigned int i;
>>> +
>>
>> I'm pretty sure I mentioned this before.  If you cant find a
>> matching rate, you return the closest rate *less than* what
>> is requested, which might not be as close as the closest
>> rate *greater than* the requested rate.
> 
> I don't see CCF requires round_rate to yield a rate that is greater than
> the request value, but only a closest one. Am i missing something?

Looking at this function again, it seems you might as
well simply do this:

{
	return rate;
}

As it is, you loop through all the rates and exit the loop
if you find an entry whose rate is higher than the one passed
as an argument.

But... that's it.  You return rate, and never change it during
the function.


Aside from that, my point was more about what I *think* you
intended this function to do, which is to find the closest
rate to the one provided.  I was suggesting you were picking
the first entry found less than or equal to the requested
rate, but that wasn't necessarily the closest.

Rather than describe what I mean, I'll provide some code.

     unsigned long best_delta = ULONG_MAX;
     long best_rate = rate;    /* maybe -EERROR? */

     for (i = 0; i < params->tbl_size; i++) {
	unsigned long table_rate = params->rate_tbl[i].rate;
         unsigned long delta = abs_diff(table_rate, rate);

         if (delta < best_delta) {
             best_delta = delta;
             best_rate = table_rate;
         }
	if (table_rate > rate)
             break;         /* if your table is sorted by rate */
     }

     return best_rate;

>> In ccu_mix_calc_best_rate() you actually take this into account
>> (though you use DIV_ROUND_UP_ULL() there too).
>>
>>> +	for (i = 0; i < params->tbl_size; i++) {
>>> +		if (params->rate_tbl[i].rate > rate) {
>>> +			i--;
>>> +			break;
>>> +		}
>>> +	}
>>> +
>>> +	return rate;
>>> +}
> 
> ...
> 
>>> diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h
>>> new file mode 100644
>>> index 000000000000..c6a3a5cce995
>>> --- /dev/null
>>> +++ b/drivers/clk/spacemit/ccu_pll.h
> 
> ...
> 
>>> +struct ccu_pll_config {
>>> +	const struct ccu_pll_rate_tbl *rate_tbl;
>>> +	u32 tbl_size;
>>
>> I dislike "size" as a name for something that is a "count".
>> To me, "size" is a number of bytes.
>>
>> You (and clock code) use "num" elsewhere, but either way,
>> please use something different from "size" here.
> 
> Of course "num" works for me.
> 
> Thank you for the detailed review! I'm pretty busy these days and
> delayed my reply. I'll try to send a v7 this week with all the comments
> resolved.

And thank you for your good responses.

					-Alex

> Best regards,
> Haylen Chu
> 
> [1]: https://en.cppreference.com/w/c/language/compound_literal
> 


_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

* Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC
  2025-03-28 14:00       ` Alex Elder
@ 2025-03-29 10:21         ` Haylen Chu
  0 siblings, 0 replies; 26+ messages in thread
From: Haylen Chu @ 2025-03-29 10:21 UTC (permalink / raw)
  To: Alex Elder, Michael Turquette, Stephen Boyd, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan
  Cc: linux-riscv, linux-clk, devicetree, linux-kernel, spacemit,
	Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu

On Fri, Mar 28, 2025 at 09:00:40AM -0500, Alex Elder wrote:
> On 3/24/25 6:14 AM, Haylen Chu wrote:
> > On Tue, Mar 11, 2025 at 06:19:51PM -0500, Alex Elder wrote:
> > > On 3/6/25 11:57 AM, Haylen Chu wrote:
> > > > The clock tree of K1 SoC contains three main types of clock hardware
> > > > (PLL/DDN/MIX) and has control registers split into several multifunction
> > > > devices: APBS (PLLs), MPMU, APBC and APMU.
> > > > 
> > > > All register operations are done through regmap to ensure atomiciy
> > > > between concurrent operations of clock driver and reset,
> > > > power-domain driver that will be introduced in the future.
> > > > 
> > > > Signed-off-by: Haylen Chu <heylenay@4d2.org>
> > > 
> > > I'm very glad you have the DT issues resolved now.
> > > 
> > > I again have lots of comments on the code, and I think I've
> > > identified a few bugs.  Most of my comments, however, are
> > > suggesting minor changes for consistency and readability.
> > > 
> > > I'm going to skip over a lot of "ccu-k1.c" because most of what I
> > > say applies to the definitions in the header files.
> 
> Sorry I didn't respond to this earlier.

I've already started to work on the v6, so it doesn't matter. I'll also
cover some new decisions made during improving v6 in the reply.

> > > > ---
> > > >    drivers/clk/Kconfig               |    1 +
> > > >    drivers/clk/Makefile              |    1 +
> > > >    drivers/clk/spacemit/Kconfig      |   20 +
> > > >    drivers/clk/spacemit/Makefile     |    5 +
> > > >    drivers/clk/spacemit/ccu-k1.c     | 1714 +++++++++++++++++++++++++++++
> > > >    drivers/clk/spacemit/ccu_common.h |   47 +
> > > >    drivers/clk/spacemit/ccu_ddn.c    |   80 ++
> > > >    drivers/clk/spacemit/ccu_ddn.h    |   48 +
> > > >    drivers/clk/spacemit/ccu_mix.c    |  284 +++++
> > > >    drivers/clk/spacemit/ccu_mix.h    |  246 +++++
> > > >    drivers/clk/spacemit/ccu_pll.c    |  146 +++
> > > >    drivers/clk/spacemit/ccu_pll.h    |   76 ++
> > > >    12 files changed, 2668 insertions(+)
> > > >    create mode 100644 drivers/clk/spacemit/Kconfig
> > > >    create mode 100644 drivers/clk/spacemit/Makefile
> > > >    create mode 100644 drivers/clk/spacemit/ccu-k1.c
> > > >    create mode 100644 drivers/clk/spacemit/ccu_common.h
> > > >    create mode 100644 drivers/clk/spacemit/ccu_ddn.c
> > > >    create mode 100644 drivers/clk/spacemit/ccu_ddn.h
> > > >    create mode 100644 drivers/clk/spacemit/ccu_mix.c
> > > >    create mode 100644 drivers/clk/spacemit/ccu_mix.h
> > > >    create mode 100644 drivers/clk/spacemit/ccu_pll.c
> > > >    create mode 100644 drivers/clk/spacemit/ccu_pll.h
> > > > 
> > 
> > ...
> > 
> > > > diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c
> > > > new file mode 100644
> > > > index 000000000000..5974a0a1b5f6
> > > > --- /dev/null
> > > > +++ b/drivers/clk/spacemit/ccu-k1.c

...

> > > > diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h
> > > > new file mode 100644
> > > > index 000000000000..494cde96fe3c
> > > > --- /dev/null
> > > > +++ b/drivers/clk/spacemit/ccu_common.h
> > > > @@ -0,0 +1,47 @@
> > > > +/* SPDX-License-Identifier: GPL-2.0-only */
> > > > +/*
> > > > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd
> > > > + * Copyright (c) 2024 Haylen Chu <heylenay@4d2.org>
> > > > + */
> > > > +
> > > > +#ifndef _CCU_COMMON_H_
> > > > +#define _CCU_COMMON_H_
> > > > +
> > > > +#include <linux/regmap.h>
> > > > +
> > > 
> > > I'm not going to suggest it at this point, but it might
> > > have worked out more nicely if you defined a top-level CCU
> > > structure that contained a union of structs, one for each
> > > type of clock (PLL, DDN, mix).
> > > 
> > > > +struct ccu_common {
> > > > +	struct regmap *regmap;
> > > > +	struct regmap *lock_regmap;
> > > 
> > > The lock_regmap is only used for PLL type clocks, right?
> > > So it could be included in the PLL struct within the union
> > > below?
> > 
> > This makes sense to me.
> 
> It might make sense but in part my suggestion makes more
> sense when taken with the comment I had above (about having
> a CCU structure with a union rather than separate per-type
> structures).  I think that would be better, but again I
> don't want you to have to do all that work if it means
> delaying getting your code accepted.
> 
> So move it to the union if that works, but for now it's
> fine the way it is.

I'll prefer to keep the regmap lock in the toplevel structure for now.
Moving the pointer into the union requires special care: we cannot
simply assign lock_regmap with NULL anymore in spacemit_ccu_register()
without checking the subtype of clk_hw, or part of the mix/ddn's struct
may be overwritten.

Keeping lock_regmap out of the union ensures the code is simple and
makes it less possible to accidentally mess the union up.

> > > > +
> > > > +	union {
> > > > +		/* For DDN and MIX */
> > > > +		struct {
> > > > +			u32 reg_ctrl;
> > > > +			u32 reg_fc;
> > > > +			u32 fc;

...

> > > 
> > > You define PLL_SWCR3_EN in "ccu_pll.c" to have value BIT(31).
> > > that's good.  But you should define its inverse, to define
> > > which bits in the reg_swcr3 field are the valid "magic" part.
> > > In both cases, I would define them here in this file, where
> > > the structure type is defined (not in "ccu_pll.c").
> > > #define SPACEMIT_PLL_SWCR3_EN	(u32)BIT(31)
> > > #define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)

I changed my mind and consider it's better to keep these macros in
ccu_pll.c, since it isn't that useful outside of the file (knowing the
exact definition of swcr3 doesn't help much, as we're really defining
some magic values). It's not a strong opinion anyway.

> > > > +		};
> > > > +	};
> > > > +
> > > > +	struct clk_hw hw;
> > > > +};

...

> > > > diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c
> > > > new file mode 100644
> > > > index 000000000000..ee187687d0c4
> > > > --- /dev/null
> > > > +++ b/drivers/clk/spacemit/ccu_ddn.c
> > > > @@ -0,0 +1,80 @@
> > 
> > ...
> > 
> > > > +static unsigned long clk_ddn_calc_best_rate(struct ccu_ddn *ddn,
> > > > +					    unsigned long rate, unsigned long prate,
> > > > +					    unsigned long *num, unsigned long *den)
> > > > +{
> > > > +	rational_best_approximation(rate, prate / 2,
> > > > +				    ddn->den_mask, ddn->num_mask,
> > > > +				    den, num);
> > > 
> > > Using rational_best_approximation() is excellent.  However I
> > > think you have a bug, and I don't think the exact way you're
> > > using it is clear (and might be wrong).
> > > 
> > > The bug is that the third and fourth arguments are the maximum
> > > numerator and denominator, respectively.  You are passing mask
> > > values, which in some sense represent the maximums.  However,
> > > your masks are not always in the low-order bits.  Here is one
> > > example:
> > > 
> > > static CCU_DDN_DEFINE(slow_uart1_14p74, pll1_d16_153p6,
> > >                        MPMU_SUCCR,
> > >                        GENMASK(28, 16), 16, GENMASK(12, 0), 0,
> > >                        0);
> > > 
> > > The "_num_mask" argument to this macro is 0x1fff0000, and the
> > > "_den_mask" is 0x00000fff.  The latter value (which gets passed
> > > as the max_numerator argument to rational_best_approximation())
> > > is fine, but the former is not.  So you need to shift both masks
> > > right by their corresponding shift value.
> > 
> > Thanks for finding it! I forget to change the function when redefining
> > struct ccu_ddn.
> > 
> > > Beyond that bug, rational_best_approximation() wants its first
> > > two arguments to define the desired rate (as a fraction).
> > 
> > I don't think it's the way that rational_best_approximation() works.
> > The first two arguments define the desired fraction, not the rate.
> > 
> > > So the desired rate should be the actual desired rate divided by 1
> > > (rather than being divided by the half the parent rate).  So
> > > this too might be a bug.
> 
> OK I took another look at this.  And I looked at the first commit
> that used this function to understand how to use it:
>   534fca068ec80 imx: serial: use rational library function
> 
> You want to know what are the best available numerator and
> denominator values to use (which fit into your register fields).
> These should be as close as possible to the fraction you're after.
> 
>     Fout = Fin * num / denom
> 
> Fin is the parent rate (always divided by two in this case),
> or "prate / 2".  Fout is the desired rate, or "rate".  You might
> get a better result if you express the "/ 2" in the parent rate
> as "* 2" in the desired rate.
> 
>     num_max = ddn->num_mask >> __ffs(ddn->num_mask);
>     den_max = ddn->den_mask >> __ffs(ddn->den_mask);
>     rational_best_approximation(rate * 2, prate,
> 				num_max, den_max, &num, &den)

Thanks, this should help :)

...

> > > > diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c
> > > > new file mode 100644
> > > > index 000000000000..9df2149f6c98
> > > > --- /dev/null
> > > > +++ b/drivers/clk/spacemit/ccu_pll.c

...

> > > > +/* frequency unit Mhz, return pll vco freq */
> > > > +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw)
> > > > +{
> > > > +	const struct ccu_pll_rate_tbl *pll_rate_table;
> > > > +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> > > > +	struct ccu_common *common = &p->common;
> > > > +	u32 swcr1, swcr3, size;
> > > > +	int i;
> > > > +
> > > > +	ccu_read(swcr1, common, &swcr1);
> > > > +	ccu_read(swcr3, common, &swcr3);
> > > 
> > > You are masking off the EN bit, but you should really be
> > > using a mask defining which bits are valid instead.  As
> > > I said earlier:
> > > 
> > > #define SPACEMIT_PLL_SWCR3_MASK	~(SPACEMIT_PLL_SWCR3_EN)
> > > 
> > > > +	swcr3 &= ~PLL_SWCR3_EN;
> > > 
> > > 	swcr3 &= SPACEMIT_PLL_SWCR3_MASK;
> > > > +
> > > > +	pll_rate_table = p->pll.rate_tbl;
> > > > +	size = p->pll.tbl_size;
> > > > +
> > > > +	for (i = 0; i < size; i++) {
> > > > +		if (pll_rate_table[i].swcr1 == swcr1 &&
> > > > +		    pll_rate_table[i].swcr3 == swcr3)
> > > > +			return pll_rate_table[i].rate;
> > > > +	}
> > > > +
> > > 
> > > I have a general question here.  Once you set one of these
> > > clock rates, it will always use one of the rates defined
> > > in the table.
> > > 
> > > But what about initially?  Could the hardware start in a
> > > state that is not defined by this code?  Do you *set* the
> > > rate initially?  Should you (at least the first time the
> > > clock is prepared/enabled)?
> > 
> > Thanks, I've also seen your later report. Here we may support
> > clk_ops.init and reinitialize the PLL if it's not in a good state.
> > 
> > Could you please provide a possible reproducing scenario for me to test
> > against the PLL problem?
> 
> What I can tell you is that I see the warning, perhaps when
> I'm using the clock.  I'll try to narrow down a test case but
> right now I don't have one.
> 
> [    0.145906] WARNING: CPU: 0 PID: 1 at drivers/clk/spacemit/ccu_pll.c:51
> ccu_pll_recalc_rate+0x76/0x9a
> 
> I added code to report the swcr1 and swcr3 values but I don't
> have those right now.

I guess I've roughly located the cause, the warning is probably
triggered by PLL3,

- Comparing with vendor U-Boot, the driver is missing three or four
  entries and one of them matches the SWCR1 value (0x0050cd61)
- There's a typo when defining PLL3,

+static CCU_PLL_DEFINE(pll3, pll3_rate_tbl,
+                     APBS_PLL3_SWCR1, APBS_PLL2_SWCR3,
+                     MPMU_POSR, POSR_PLL3_LOCK, CLK_SET_RATE_GATE);

  here APBS_PLL2_SWCR3 should be APBS_PLL3_SWCR3. This explains the
  value of SWCR3 (0x3fe00000) in your case, where it's actually read
  from PLL2's register.

I'll add the missing configuration entries and fix the typo in v6.

> > 
> > > > +	WARN_ON_ONCE(1);
> > > 
> > > Maybe WARN_ONCE(true, "msg");
> > > 
> > > > +
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int ccu_pll_enable(struct clk_hw *hw)
> > > > +{
> > > > +	struct ccu_pll *p = hw_to_ccu_pll(hw);
> > > > +	struct ccu_common *common = &p->common;
> > > > +	unsigned int tmp;
> > > > +	int ret;
> > > > +
> > > 
> > > Get rid of ret (see below).
> > > 
> > > Will clk_ops->enable() ever be called when it's already
> > > enabled?  (If it won't, this isn't needed.  If it will,
> > > this checks the hardware, which is good.)
> > 
> > CCF holds a refcounter of clock consumers, so we could drop the check.

I didn't expect calls to ccu_pll_enable() may happen when the
previous stage bootloaders have already initialized the PLL. So the
right anwswer is yes, clk_ops->enable() may be called when the
underlying clock hardware has been enabled, but remove the check
shouldn't hurt, either: it's okay to rewrite the register as long as
we don't change rate-related settings if the PLL is enabled.

> > > > +	if (ccu_pll_is_enabled(hw))
> > > > +		return 0;
> > > > +
> > > > +	ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN);
> > > > +
> > > > +	/* check lock status */
> > > > +	ret = regmap_read_poll_timeout_atomic(common->lock_regmap,
> > > > +					      p->pll.reg_lock,
> > > > +					      tmp,
> > > > +					      tmp & p->pll.lock_enable_bit,
> > > > +					      5, PLL_DELAY_TIME);
> > > 
> > > Just:
> > > 
> > > 	return regmap_read_poll_timeout_atomic(...);
> > > 
> > > I note that you call this here, but you hide the call
> > > to regmap_read_poll_timeout_atomic() behind the macro
> > > ccu_poll().  And ccu_poll() (used for the FC bit) is
> > > also only called once.
> > > 
> > > I suggest you get rid of regmap_poll() and just open-code
> > > it.
> > > 
> > > (You use ccu_read() and ccu_update() numerous times, so
> > > your "saving some characters" is justified.)
> > 
> > Makes sense to me, will take it.
> > 
> > > > +
> > > > +	return ret;
> > > > +}

Thanks,
Haylen Chu

_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv

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

end of thread, other threads:[~2025-03-29 10:22 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-03-06 17:57 [PATCH v5 0/5] Add clock controller support for SpacemiT K1 Haylen Chu
2025-03-06 17:57 ` [PATCH v5 1/5] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu
2025-03-07  8:16   ` Krzysztof Kozlowski
2025-03-06 17:57 ` [PATCH v5 2/5] dt-bindings: clock: spacemit: Add spacemit,k1-pll Haylen Chu
2025-03-07  0:29   ` Yixun Lan
2025-03-07  6:34     ` Haylen Chu
2025-03-07  8:20       ` Krzysztof Kozlowski
2025-03-07  8:19   ` Krzysztof Kozlowski
2025-03-06 17:57 ` [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu
2025-03-07  0:51   ` Yixun Lan
2025-03-07  6:42     ` Haylen Chu
2025-03-07  8:26       ` Krzysztof Kozlowski
2025-03-11 23:19   ` Alex Elder
2025-03-20 22:39     ` Alex Elder
2025-03-24 11:14     ` Haylen Chu
2025-03-28 14:00       ` Alex Elder
2025-03-29 10:21         ` Haylen Chu
2025-03-12 20:17   ` kernel test robot
2025-03-18  5:37   ` Yixun Lan
2025-03-18  5:43     ` Inochi Amaoto
2025-03-23  8:55       ` Haylen Chu
2025-03-06 17:57 ` [PATCH v5 4/5] clk: spacemit: k1: Add TWSI8 bus and function clocks Haylen Chu
2025-03-07  6:30   ` Haylen Chu
2025-03-06 17:57 ` [PATCH v5 5/5] riscv: dts: spacemit: Add clock tree for Spacemit K1 Haylen Chu
2025-03-07  1:55   ` Inochi Amaoto
2025-03-07  6:28     ` Haylen Chu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).