* [PATCH v4 0/4] Add clock controller support for SpacemiT K1
@ 2025-01-03 21:56 Haylen Chu
2025-01-03 21:56 ` [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC Haylen Chu
` (3 more replies)
0 siblings, 4 replies; 31+ messages in thread
From: Haylen Chu @ 2025-01-03 21:56 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, Inochi Amaoto,
Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu
The clock tree of SpacemiT K1 is managed by several independent
controllers in different SoC parts. In this series, all clock hardwares
in APBS, MPMU, APBC and APMU are implemented. Clocks in other SoC parts
could be implemented later.
This driver has been tested on BananaPi-F3 board. With some out-of-tree
drivers, I've successfully brought up I2C, RTC, MMC and ethernet
controllers, proving it works. A clock tree dump could be obtained
here[1].
[1]: https://gist.github.com/heylenayy/fda51ac6736ac97a2c53d8caa3302378
Link: https://developer.spacemit.com/documentation?token=LCrKwWDasiJuROkVNusc2pWTnEb
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 (4):
dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC
dt-bindings: soc: spacemit: Add spacemit,k1-syscon
clk: spacemit: Add clock support for Spacemit K1 SoC
riscv: dts: spacemit: Add clock controller for K1
.../bindings/clock/spacemit,k1-ccu.yaml | 60 +
.../soc/spacemit/spacemit,k1-syscon.yaml | 52 +
arch/riscv/boot/dts/spacemit/k1.dtsi | 97 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/spacemit/Kconfig | 20 +
drivers/clk/spacemit/Makefile | 5 +
drivers/clk/spacemit/ccu-k1.c | 1747 +++++++++++++++++
drivers/clk/spacemit/ccu_common.h | 51 +
drivers/clk/spacemit/ccu_ddn.c | 140 ++
drivers/clk/spacemit/ccu_ddn.h | 84 +
drivers/clk/spacemit/ccu_mix.c | 304 +++
drivers/clk/spacemit/ccu_mix.h | 309 +++
drivers/clk/spacemit/ccu_pll.c | 189 ++
drivers/clk/spacemit/ccu_pll.h | 80 +
include/dt-bindings/clock/spacemit,k1-ccu.h | 246 +++
16 files changed, 3386 insertions(+)
create mode 100644 Documentation/devicetree/bindings/clock/spacemit,k1-ccu.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
base-commit: 2d5404caa8c7bb5c4e0435f94b28834ae5456623
prerequisite-patch-id: 47dcf6861f7d434d25855b379e6d7ef4ce369c9c
prerequisite-patch-id: 77787fe82911923aff15ccf565e8fa451538c3a6
prerequisite-patch-id: b0bdb1742d96c5738f05262c3b0059102761390b
prerequisite-patch-id: 3927d39d8d77e35d5bfe53d9950da574ff8f2054
prerequisite-patch-id: a98039136a4796252a6029e474f03906f2541643
prerequisite-patch-id: c95f6dc0547a2a63a76e3cba0cf5c623b212b4e6
prerequisite-patch-id: 66e750e438ee959ddc2a6f0650814a2d8c989139
prerequisite-patch-id: 29a0fd8c36c1a4340f0d0b68a4c34d2b8abfb1ab
prerequisite-patch-id: 0bdfff661c33c380d1cf00a6c68688e05f88c0b3
prerequisite-patch-id: 99f15718e0bfbb7ed1a96dfa19f35841b004dae9
--
2.47.1
^ permalink raw reply [flat|nested] 31+ messages in thread* [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC 2025-01-03 21:56 [PATCH v4 0/4] Add clock controller support for SpacemiT K1 Haylen Chu @ 2025-01-03 21:56 ` Haylen Chu 2025-01-04 9:58 ` Krzysztof Kozlowski 2025-01-03 21:56 ` [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu ` (2 subsequent siblings) 3 siblings, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-01-03 21:56 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu Add definition for the clock controllers of Spacemit K1 SoC. The clock tree is managed by several SoC parts thus different compatible strings are introduced for each. Signed-off-by: Haylen Chu <heylenay@4d2.org> --- .../bindings/clock/spacemit,k1-ccu.yaml | 60 +++++ include/dt-bindings/clock/spacemit,k1-ccu.h | 246 ++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 Documentation/devicetree/bindings/clock/spacemit,k1-ccu.yaml create mode 100644 include/dt-bindings/clock/spacemit,k1-ccu.h diff --git a/Documentation/devicetree/bindings/clock/spacemit,k1-ccu.yaml b/Documentation/devicetree/bindings/clock/spacemit,k1-ccu.yaml new file mode 100644 index 000000000000..56db670961cf --- /dev/null +++ b/Documentation/devicetree/bindings/clock/spacemit,k1-ccu.yaml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/spacemit,k1-ccu.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Spacemit K1 SoC Clock Controller + +maintainers: + - Haylen Chu <heylenay@4d2.org> + +properties: + compatible: + enum: + - spacemit,k1-ccu-apbs + - spacemit,k1-ccu-mpmu + - spacemit,k1-ccu-apbc + - spacemit,k1-ccu-apmu + + clocks: + maxItems: 4 + + clock-names: + items: + - const: osc + - const: vctcxo_1m + - const: vctcxo_3m + - const: vctcxo_24m + + spacemit,mpmu: + $ref: /schemas/types.yaml#/definitions/phandle + description: + Phandle to the syscon managing "Main PMU (MPMU)" registers. 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 + - clocks + - clock-names + - "#clock-cells" + +allOf: + - if: + properties: + compatible: + contains: + const: spacemit,k1-ccu-apbs + then: + required: + - spacemit,mpmu + else: + properties: + spacemit,mpmu: false + +additionalProperties: false 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..71760355b304 --- /dev/null +++ b/include/dt-bindings/clock/spacemit,k1-ccu.h @@ -0,0 +1,246 @@ +/* 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_ + +/* APBS 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 +#define CLK_APBS_NUM 35 + +/* 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_351 15 +#define CLK_PLL1_409P6 16 +#define CLK_PLL1_204P8 17 +#define CLK_PLL1_491 18 +#define CLK_PLL1_245P76 19 +#define CLK_PLL1_614 20 +#define CLK_PLL1_47P26 21 +#define CLK_PLL1_31P5 22 +#define CLK_PLL1_819 23 +#define CLK_PLL1_1228 24 +#define CLK_SLOW_UART 25 +#define CLK_SLOW_UART1 26 +#define CLK_SLOW_UART2 27 +#define CLK_WDT 28 +#define CLK_RIPC 29 +#define CLK_I2S_SYSCLK 30 +#define CLK_I2S_BCLK 31 +#define CLK_APB 32 +#define CLK_WDT_BUS 33 +#define CLK_MPMU_NUM 34 + +/* 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 +#define CLK_APBC_NUM 100 + +/* 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 28 +#define CLK_PCIE1 29 +#define CLK_PCIE2 30 +#define CLK_EMAC0_BUS 31 +#define CLK_EMAC0_PTP 32 +#define CLK_EMAC1_BUS 33 +#define CLK_EMAC1_PTP 34 +#define CLK_JPG 35 +#define CLK_CCIC2PHY 36 +#define CLK_CCIC3PHY 37 +#define CLK_CSI 38 +#define CLK_CAMM0 39 +#define CLK_CAMM1 40 +#define CLK_CAMM2 41 +#define CLK_ISP_CPP 42 +#define CLK_ISP_BUS 43 +#define CLK_ISP 44 +#define CLK_DPU_MCLK 45 +#define CLK_DPU_ESC 46 +#define CLK_DPU_BIT 47 +#define CLK_DPU_PXCLK 48 +#define CLK_DPU_HCLK 49 +#define CLK_DPU_SPI 50 +#define CLK_DPU_SPI_HBUS 51 +#define CLK_DPU_SPIBUS 52 +#define CLK_DPU_SPI_ACLK 53 +#define CLK_V2D 54 +#define CLK_EMMC_BUS 55 +#define CLK_APMU_NUM 56 + +#endif /* _DT_BINDINGS_SPACEMIT_CCU_H_ */ -- 2.47.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC 2025-01-03 21:56 ` [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC Haylen Chu @ 2025-01-04 9:58 ` Krzysztof Kozlowski 2025-01-15 7:29 ` Haylen Chu 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-01-04 9:58 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Fri, Jan 03, 2025 at 09:56:34PM +0000, Haylen Chu wrote: > +#define CLK_PLL3_80 32 > +#define CLK_PLL3_40 33 > +#define CLK_PLL3_20 34 > +#define CLK_APBS_NUM 35 Binding looks fine, thanks for the changes, but now I noticed this define. Please drop. Number of clocks is rarely useful as a binding and usually leading to ABI issues. Same for all other clock controllers below. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC 2025-01-04 9:58 ` Krzysztof Kozlowski @ 2025-01-15 7:29 ` Haylen Chu 0 siblings, 0 replies; 31+ messages in thread From: Haylen Chu @ 2025-01-15 7:29 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Sat, Jan 04, 2025 at 10:58:54AM +0100, Krzysztof Kozlowski wrote: > On Fri, Jan 03, 2025 at 09:56:34PM +0000, Haylen Chu wrote: > > +#define CLK_PLL3_80 32 > > +#define CLK_PLL3_40 33 > > +#define CLK_PLL3_20 34 > > +#define CLK_APBS_NUM 35 > > Binding looks fine, thanks for the changes, but now I noticed this > define. Please drop. Number of clocks is rarely useful as a binding and > usually leading to ABI issues. > > Same for all other clock controllers below. Okay, will drop it. > > Best regards, > Krzysztof > Thanks, Haylen Chu ^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-01-03 21:56 [PATCH v4 0/4] Add clock controller support for SpacemiT K1 Haylen Chu 2025-01-03 21:56 ` [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC Haylen Chu @ 2025-01-03 21:56 ` Haylen Chu 2025-01-04 10:07 ` Krzysztof Kozlowski 2025-01-03 21:56 ` [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu 2025-01-03 21:56 ` [PATCH v4 4/4] riscv: dts: spacemit: Add clock controller for K1 Haylen Chu 3 siblings, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-01-03 21:56 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu Add documentation to describe Spacemit K1 system controller registers. Signed-off-by: Haylen Chu <heylenay@4d2.org> --- .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml 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..79c4a74ff30e --- /dev/null +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml @@ -0,0 +1,52 @@ +# 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: + The Spacemit K1 SoC system controller provides access to shared register files + for related SoC modules, such as clock controller and reset controller. + +properties: + compatible: + items: + - enum: + - spacemit,k1-apbc-syscon + - spacemit,k1-apbs-syscon + - spacemit,k1-apmu-syscon + - spacemit,k1-mpmu-syscon + - const: syscon + - const: simple-mfd + + reg: + maxItems: 1 + + clock-controller: + $ref: /schemas/clock/spacemit,k1-ccu.yaml# + type: object + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + system-controller@d4050000 { + compatible = "spacemit,k1-mpmu-syscon", "syscon", "simple-mfd"; + reg = <0xd4050000 0x209c>; + + clock-controller { + compatible = "spacemit,k1-ccu-mpmu"; + clocks = <&osc_32k>, <&vctcxo_1m>, <&vctcxo_3m>, <&vctcxo_24m>; + clock-names = "osc", "vctcxo_1m", "vctcxo_3m", "vctcxo_24m"; + #clock-cells = <1>; + }; + }; -- 2.47.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-01-03 21:56 ` [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu @ 2025-01-04 10:07 ` Krzysztof Kozlowski 2025-02-11 5:15 ` Haylen Chu 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-01-04 10:07 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Fri, Jan 03, 2025 at 09:56:35PM +0000, Haylen Chu wrote: > Add documentation to describe Spacemit K1 system controller registers. > > Signed-off-by: Haylen Chu <heylenay@4d2.org> > --- > .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ > 1 file changed, 52 insertions(+) > create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > > 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..79c4a74ff30e > --- /dev/null > +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > @@ -0,0 +1,52 @@ > +# 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: > + The Spacemit K1 SoC system controller provides access to shared register files > + for related SoC modules, such as clock controller and reset controller. > + > +properties: > + compatible: > + items: > + - enum: > + - spacemit,k1-apbc-syscon > + - spacemit,k1-apbs-syscon > + - spacemit,k1-apmu-syscon > + - spacemit,k1-mpmu-syscon > + - const: syscon > + - const: simple-mfd > + > + reg: > + maxItems: 1 > + > + clock-controller: > + $ref: /schemas/clock/spacemit,k1-ccu.yaml# > + type: object So now we see the full picture and it leads to questions. 1. Why spacemit,k1-apbc-syscon with spacemit,k1-ccu-apmu child is a correct combination? 2. Why having this split in the first place? Please confirm that clock controller is really, really a separate device and its child in datasheet. IOW, fake child for your Linux is a no-go. Fake child while devices are independent is another no-go. Actual answer for 1+2 above would be to fold the child into parent, assuming clock controller split is fake in terms of datasheet. If it is real device, then allOf:if:then: narrowing the compatibles of child might not be worth the complexity. 3. Why using different naming, look: spacemit,k1-XXXX-syscon spacemit,k1-ccu-XXXX Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-01-04 10:07 ` Krzysztof Kozlowski @ 2025-02-11 5:15 ` Haylen Chu 2025-02-11 8:03 ` Krzysztof Kozlowski 0 siblings, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-02-11 5:15 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Sat, Jan 04, 2025 at 11:07:58AM +0100, Krzysztof Kozlowski wrote: > On Fri, Jan 03, 2025 at 09:56:35PM +0000, Haylen Chu wrote: > > Add documentation to describe Spacemit K1 system controller registers. > > > > Signed-off-by: Haylen Chu <heylenay@4d2.org> > > --- > > .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ > > 1 file changed, 52 insertions(+) > > create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > > > > 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..79c4a74ff30e > > --- /dev/null > > +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > > @@ -0,0 +1,52 @@ > > +# 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: > > + The Spacemit K1 SoC system controller provides access to shared register files > > + for related SoC modules, such as clock controller and reset controller. > > + > > +properties: > > + compatible: > > + items: > > + - enum: > > + - spacemit,k1-apbc-syscon > > + - spacemit,k1-apbs-syscon > > + - spacemit,k1-apmu-syscon > > + - spacemit,k1-mpmu-syscon > > + - const: syscon > > + - const: simple-mfd > > + > > + reg: > > + maxItems: 1 > > + > > + clock-controller: > > + $ref: /schemas/clock/spacemit,k1-ccu.yaml# > > + type: object > > So now we see the full picture and it leads to questions. > > 1. Why spacemit,k1-apbc-syscon with spacemit,k1-ccu-apmu child is a > correct combination? > > 2. Why having this split in the first place? Please confirm that clock > controller is really, really a separate device and its child in > datasheet. IOW, fake child for your Linux is a no-go. Fake child while > devices are independent is another no-go. These syscons are introduced because the clock controllers share registers with reset controllers. Folding them into the parents results in devicetree nodes act as both reset and clock controllers, like what has been done for Rockchip SoCs. Such folding isn't practical for the MPMU region either, since watchdog and other misc bits (e.g. PLL lock status) locates in it. If you're more comfortable with reset and clock controllers folded together and eliminating most of these syscons, I'm willing to make the change. > Actual answer for 1+2 above would be to fold the child into parent, > assuming clock controller split is fake in terms of datasheet. > > If it is real device, then allOf:if:then: narrowing the compatibles of > child might not be worth the complexity. > > 3. Why using different naming, look: > > spacemit,k1-XXXX-syscon > spacemit,k1-ccu-XXXX I didn't consider about consistency when naming them. Talked to Yixun, I'll unify them as spacemit,k1-syscon-* and spacemit,k1-ccu-*, keeping synchronized with other K1 peripherals. > > Best regards, > Krzysztof > Thanks, Haylen Chu ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-11 5:15 ` Haylen Chu @ 2025-02-11 8:03 ` Krzysztof Kozlowski 2025-02-13 11:14 ` Haylen Chu 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-02-11 8:03 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On 11/02/2025 06:15, Haylen Chu wrote: > On Sat, Jan 04, 2025 at 11:07:58AM +0100, Krzysztof Kozlowski wrote: >> On Fri, Jan 03, 2025 at 09:56:35PM +0000, Haylen Chu wrote: >>> Add documentation to describe Spacemit K1 system controller registers. >>> >>> Signed-off-by: Haylen Chu <heylenay@4d2.org> >>> --- >>> .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ >>> 1 file changed, 52 insertions(+) >>> create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml >>> >>> 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..79c4a74ff30e >>> --- /dev/null >>> +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml >>> @@ -0,0 +1,52 @@ >>> +# 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: >>> + The Spacemit K1 SoC system controller provides access to shared register files >>> + for related SoC modules, such as clock controller and reset controller. >>> + >>> +properties: >>> + compatible: >>> + items: >>> + - enum: >>> + - spacemit,k1-apbc-syscon >>> + - spacemit,k1-apbs-syscon >>> + - spacemit,k1-apmu-syscon >>> + - spacemit,k1-mpmu-syscon >>> + - const: syscon >>> + - const: simple-mfd >>> + >>> + reg: >>> + maxItems: 1 >>> + >>> + clock-controller: >>> + $ref: /schemas/clock/spacemit,k1-ccu.yaml# >>> + type: object >> >> So now we see the full picture and it leads to questions. >> >> 1. Why spacemit,k1-apbc-syscon with spacemit,k1-ccu-apmu child is a >> correct combination? >> >> 2. Why having this split in the first place? Please confirm that clock >> controller is really, really a separate device and its child in >> datasheet. IOW, fake child for your Linux is a no-go. Fake child while >> devices are independent is another no-go. > > These syscons are introduced because the clock controllers share > registers with reset controllers. Folding them into the parents results So a fake split... > in devicetree nodes act as both reset and clock controllers, like what Which is correct hardware representation, isn't it? > has been done for Rockchip SoCs. Such folding isn't practical for the > MPMU region either, since watchdog and other misc bits (e.g. PLL lock > status) locates in it. Hm? Why? You have a device which is reset and clock controller, so why one device node is not practical? Other vendors do not have problem with this. > > If you're more comfortable with reset and clock controllers folded > together and eliminating most of these syscons, I'm willing to make the > change. This is expected. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-11 8:03 ` Krzysztof Kozlowski @ 2025-02-13 11:14 ` Haylen Chu 2025-02-13 18:07 ` Krzysztof Kozlowski 0 siblings, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-02-13 11:14 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Tue, Feb 11, 2025 at 09:03:20AM +0100, Krzysztof Kozlowski wrote: > On 11/02/2025 06:15, Haylen Chu wrote: > > On Sat, Jan 04, 2025 at 11:07:58AM +0100, Krzysztof Kozlowski wrote: > >> On Fri, Jan 03, 2025 at 09:56:35PM +0000, Haylen Chu wrote: > >>> Add documentation to describe Spacemit K1 system controller registers. > >>> > >>> Signed-off-by: Haylen Chu <heylenay@4d2.org> > >>> --- > >>> .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ > >>> 1 file changed, 52 insertions(+) > >>> create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > >>> > >>> 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..79c4a74ff30e > >>> --- /dev/null > >>> +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > >>> @@ -0,0 +1,52 @@ > >>> +# 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: > >>> + The Spacemit K1 SoC system controller provides access to shared register files > >>> + for related SoC modules, such as clock controller and reset controller. > >>> + > >>> +properties: > >>> + compatible: > >>> + items: > >>> + - enum: > >>> + - spacemit,k1-apbc-syscon > >>> + - spacemit,k1-apbs-syscon > >>> + - spacemit,k1-apmu-syscon > >>> + - spacemit,k1-mpmu-syscon > >>> + - const: syscon > >>> + - const: simple-mfd > >>> + > >>> + reg: > >>> + maxItems: 1 > >>> + > >>> + clock-controller: > >>> + $ref: /schemas/clock/spacemit,k1-ccu.yaml# > >>> + type: object > >> > >> So now we see the full picture and it leads to questions. > >> > >> 1. Why spacemit,k1-apbc-syscon with spacemit,k1-ccu-apmu child is a > >> correct combination? > >> > >> 2. Why having this split in the first place? Please confirm that clock > >> controller is really, really a separate device and its child in > >> datasheet. IOW, fake child for your Linux is a no-go. Fake child while > >> devices are independent is another no-go. > > > > These syscons are introduced because the clock controllers share > > registers with reset controllers. Folding them into the parents results > > So a fake split... > > > in devicetree nodes act as both reset and clock controllers, like what > > Which is correct hardware representation, isn't it? > > > has been done for Rockchip SoCs. Such folding isn't practical for the > > MPMU region either, since watchdog and other misc bits (e.g. PLL lock > > status) locates in it. I have to correct that the watchdog doesn't stay in the MPMU region, I misremembered it. > Hm? Why? You have a device which is reset and clock controller, so why > one device node is not practical? Other vendors do not have problem with > this. Merging reset and clock controllers together is fine to me. What I want to mention is that APMU and MPMU, abbreviated from Application/Main Power Management Unit, contain not only clock/reset-related registers but also power management ones[1]. Additionally, the PLL lock status bits locate at MPMU, split from the PLL configuration registers as you've already seen in the binding of spacemit,k1-ccu-apbs where I refer to it with a phandle. Since reset/clock and power management registers interleave in the MMIO region, do you think syscons are acceptable in this situation or it should be handled in another way? The reset and clock controllers could still be folded together as they share the same registers. The device tree will look like, syscon_mpmu: system-controller@d4050000 { compatible = "spacemit,mpmu-syscon", "syscon", "simple-mfd"; reg = <0xd4050000 0x10000>; cru_mpmu: clock-controller { compatible = "spacemit,k1-cru-mpmu"; #clock-cells = <1>; #reset-cells = <1>; }; power_mpmu: power-controller { compatible = "spacemit,k1-powerdomain-mpmu"; /* ... */ #power-domain-cells = <0>; }; }; For the other two clock controllers (APBS and APBC), syscons are really unnecessary and it's simple to fold them. > > > > If you're more comfortable with reset and clock controllers folded > > together and eliminating most of these syscons, I'm willing to make the > > change. > > This is expected. Thanks for the explanation. > > > Best regards, > Krzysztof Best regards, Haylen Chu [1]: https://developer.spacemit.com/documentation?token=T7TnwVZz1iPBk1kKwAPc6lyKnNb#part958 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-13 11:14 ` Haylen Chu @ 2025-02-13 18:07 ` Krzysztof Kozlowski 2025-02-15 8:41 ` Haylen Chu 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-02-13 18:07 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On 13/02/2025 12:14, Haylen Chu wrote: > On Tue, Feb 11, 2025 at 09:03:20AM +0100, Krzysztof Kozlowski wrote: >> On 11/02/2025 06:15, Haylen Chu wrote: >>> On Sat, Jan 04, 2025 at 11:07:58AM +0100, Krzysztof Kozlowski wrote: >>>> On Fri, Jan 03, 2025 at 09:56:35PM +0000, Haylen Chu wrote: >>>>> Add documentation to describe Spacemit K1 system controller registers. >>>>> >>>>> Signed-off-by: Haylen Chu <heylenay@4d2.org> >>>>> --- >>>>> .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ >>>>> 1 file changed, 52 insertions(+) >>>>> create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml >>>>> >>>>> 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..79c4a74ff30e >>>>> --- /dev/null >>>>> +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml >>>>> @@ -0,0 +1,52 @@ >>>>> +# 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: >>>>> + The Spacemit K1 SoC system controller provides access to shared register files >>>>> + for related SoC modules, such as clock controller and reset controller. >>>>> + >>>>> +properties: >>>>> + compatible: >>>>> + items: >>>>> + - enum: >>>>> + - spacemit,k1-apbc-syscon >>>>> + - spacemit,k1-apbs-syscon >>>>> + - spacemit,k1-apmu-syscon >>>>> + - spacemit,k1-mpmu-syscon >>>>> + - const: syscon >>>>> + - const: simple-mfd >>>>> + >>>>> + reg: >>>>> + maxItems: 1 >>>>> + >>>>> + clock-controller: >>>>> + $ref: /schemas/clock/spacemit,k1-ccu.yaml# >>>>> + type: object >>>> >>>> So now we see the full picture and it leads to questions. >>>> >>>> 1. Why spacemit,k1-apbc-syscon with spacemit,k1-ccu-apmu child is a >>>> correct combination? >>>> >>>> 2. Why having this split in the first place? Please confirm that clock >>>> controller is really, really a separate device and its child in >>>> datasheet. IOW, fake child for your Linux is a no-go. Fake child while >>>> devices are independent is another no-go. >>> >>> These syscons are introduced because the clock controllers share >>> registers with reset controllers. Folding them into the parents results >> >> So a fake split... >> >>> in devicetree nodes act as both reset and clock controllers, like what >> >> Which is correct hardware representation, isn't it? >> >>> has been done for Rockchip SoCs. Such folding isn't practical for the >>> MPMU region either, since watchdog and other misc bits (e.g. PLL lock >>> status) locates in it. > > I have to correct that the watchdog doesn't stay in the MPMU region, I > misremembered it. > >> Hm? Why? You have a device which is reset and clock controller, so why >> one device node is not practical? Other vendors do not have problem with >> this. > > Merging reset and clock controllers together is fine to me. What I want > to mention is that APMU and MPMU, abbreviated from Application/Main Power > Management Unit, contain not only clock/reset-related registers but also > power management ones[1]. Additionally, the PLL lock status bits locate > at MPMU, split from the PLL configuration registers as you've already > seen in the binding of spacemit,k1-ccu-apbs where I refer to it with a > phandle. You need to define what is the device here. Don't create fake nodes just for your drivers. If registers are interleaved and manual says "this is block APMU/MPMU" then you have one device, so one node with 'reg'. If subblocks are re-usable hardware (unlikely) or at least separate/distinguishable, you could have children. If subblocks are re-usable but addresses are interleaved, then children should not have 'reg'. If children do not have any resources as an effect, this is strong indication these are not re-usable, separate subblocks. > > Since reset/clock and power management registers interleave in the MMIO > region, do you think syscons are acceptable in this situation or it > should be handled in another way? The reset and clock controllers could > still be folded together as they share the same registers. The device > tree will look like, > > syscon_mpmu: system-controller@d4050000 { > compatible = "spacemit,mpmu-syscon", "syscon", "simple-mfd"; > reg = <0xd4050000 0x10000>; > > cru_mpmu: clock-controller { > compatible = "spacemit,k1-cru-mpmu"; > #clock-cells = <1>; > #reset-cells = <1>; > }; > > power_mpmu: power-controller { > compatible = "spacemit,k1-powerdomain-mpmu"; > /* ... */ > #power-domain-cells = <0>; > }; Based on above, I do not see any need for children device nodes. It's fake split to match driver design. > }; > > For the other two clock controllers (APBS and APBC), syscons are really > unnecessary and it's simple to fold them. I don't follow. Do we talk about children or syscon compatible? Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-13 18:07 ` Krzysztof Kozlowski @ 2025-02-15 8:41 ` Haylen Chu 2025-02-21 23:40 ` Alex Elder 2025-02-22 9:52 ` Krzysztof Kozlowski 0 siblings, 2 replies; 31+ messages in thread From: Haylen Chu @ 2025-02-15 8:41 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Thu, Feb 13, 2025 at 07:07:55PM +0100, Krzysztof Kozlowski wrote: > On 13/02/2025 12:14, Haylen Chu wrote: > > On Tue, Feb 11, 2025 at 09:03:20AM +0100, Krzysztof Kozlowski wrote: > >> On 11/02/2025 06:15, Haylen Chu wrote: > >>> On Sat, Jan 04, 2025 at 11:07:58AM +0100, Krzysztof Kozlowski wrote: > >>>> On Fri, Jan 03, 2025 at 09:56:35PM +0000, Haylen Chu wrote: > >>>>> Add documentation to describe Spacemit K1 system controller registers. > >>>>> > >>>>> Signed-off-by: Haylen Chu <heylenay@4d2.org> > >>>>> --- > >>>>> .../soc/spacemit/spacemit,k1-syscon.yaml | 52 +++++++++++++++++++ > >>>>> 1 file changed, 52 insertions(+) > >>>>> create mode 100644 Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > >>>>> > >>>>> 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..79c4a74ff30e > >>>>> --- /dev/null > >>>>> +++ b/Documentation/devicetree/bindings/soc/spacemit/spacemit,k1-syscon.yaml > >>>>> @@ -0,0 +1,52 @@ > >>>>> +# 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: > >>>>> + The Spacemit K1 SoC system controller provides access to shared register files > >>>>> + for related SoC modules, such as clock controller and reset controller. > >>>>> + > >>>>> +properties: > >>>>> + compatible: > >>>>> + items: > >>>>> + - enum: > >>>>> + - spacemit,k1-apbc-syscon > >>>>> + - spacemit,k1-apbs-syscon > >>>>> + - spacemit,k1-apmu-syscon > >>>>> + - spacemit,k1-mpmu-syscon > >>>>> + - const: syscon > >>>>> + - const: simple-mfd > >>>>> + > >>>>> + reg: > >>>>> + maxItems: 1 > >>>>> + > >>>>> + clock-controller: > >>>>> + $ref: /schemas/clock/spacemit,k1-ccu.yaml# > >>>>> + type: object > >>>> > >>>> So now we see the full picture and it leads to questions. > >>>> > >>>> 1. Why spacemit,k1-apbc-syscon with spacemit,k1-ccu-apmu child is a > >>>> correct combination? > >>>> > >>>> 2. Why having this split in the first place? Please confirm that clock > >>>> controller is really, really a separate device and its child in > >>>> datasheet. IOW, fake child for your Linux is a no-go. Fake child while > >>>> devices are independent is another no-go. > >>> > >>> These syscons are introduced because the clock controllers share > >>> registers with reset controllers. Folding them into the parents results > >> > >> So a fake split... > >> > >>> in devicetree nodes act as both reset and clock controllers, like what > >> > >> Which is correct hardware representation, isn't it? > >> > >>> has been done for Rockchip SoCs. Such folding isn't practical for the > >>> MPMU region either, since watchdog and other misc bits (e.g. PLL lock > >>> status) locates in it. > > > > I have to correct that the watchdog doesn't stay in the MPMU region, I > > misremembered it. > > > >> Hm? Why? You have a device which is reset and clock controller, so why > >> one device node is not practical? Other vendors do not have problem with > >> this. > > > > Merging reset and clock controllers together is fine to me. What I want > > to mention is that APMU and MPMU, abbreviated from Application/Main Power > > Management Unit, contain not only clock/reset-related registers but also > > power management ones[1]. Additionally, the PLL lock status bits locate > > at MPMU, split from the PLL configuration registers as you've already > > seen in the binding of spacemit,k1-ccu-apbs where I refer to it with a > > phandle. > > You need to define what is the device here. Don't create fake nodes just > for your drivers. If registers are interleaved and manual says "this is > block APMU/MPMU" then you have one device, so one node with 'reg'. > > If subblocks are re-usable hardware (unlikely) or at least > separate/distinguishable, you could have children. If subblocks are > re-usable but addresses are interleaved, then children should not have > 'reg'. If children do not have any resources as an effect, this is > strong indication these are not re-usable, separate subblocks. > > > > > Since reset/clock and power management registers interleave in the MMIO > > region, do you think syscons are acceptable in this situation or it > > should be handled in another way? The reset and clock controllers could > > still be folded together as they share the same registers. The device > > tree will look like, > > > > syscon_mpmu: system-controller@d4050000 { > > compatible = "spacemit,mpmu-syscon", "syscon", "simple-mfd"; > > reg = <0xd4050000 0x10000>; > > > > cru_mpmu: clock-controller { > > compatible = "spacemit,k1-cru-mpmu"; > > #clock-cells = <1>; > > #reset-cells = <1>; > > }; > > > > power_mpmu: power-controller { > > compatible = "spacemit,k1-powerdomain-mpmu"; > > /* ... */ > > #power-domain-cells = <0>; > > }; > > Based on above, I do not see any need for children device nodes. It's > fake split to match driver design. Okay, I'll make APMU/MPMU act as a whole device without split children and drop bindings for the childern (spacemit,k1-ccu-mpmu) in the next revision. Do I get the point? > > }; > > > > For the other two clock controllers (APBS and APBC), syscons are really > > unnecessary and it's simple to fold them. > > > I don't follow. Do we talk about children or syscon compatible? APBS region contains only clock (PLL) bits and APBC region contains only reset and clock bits, so I was thinking about dropping the syscon nodes and changing their compatible to spacemit,k1-plls and spacemit,k1-cru-apbc. In summary, my plan is, - For MPMU, APMU and APBC region, keep the binding in soc/spacemit. They'll be reset, clock and power controllers, with compatible "spacemit,k1-syscon-*". - For APBS region, write a new binding clock/spacemit,k1-plls, as it contains only PLL-related bits. It acts as clock controller. - All split children will be eliminated, there'll be only four device nodes, one for each region, matching the datasheet. - Put all clock-related binding definition of SpacemiT K1 in dt-bindings/clock/spacemit,k1-ccu.h Is it fine for you? > > Best regards, > Krzysztof Thanks, Haylen Chu ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-15 8:41 ` Haylen Chu @ 2025-02-21 23:40 ` Alex Elder 2025-02-22 9:59 ` Krzysztof Kozlowski 2025-02-22 9:52 ` Krzysztof Kozlowski 1 sibling, 1 reply; 31+ messages in thread From: Alex Elder @ 2025-02-21 23:40 UTC (permalink / raw) To: Haylen Chu, Krzysztof Kozlowski Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu I have a general proposal on how to represent this, but I'd like to know whether it makes sense. It might be what Krzysztof is suggesting, but in any case, I hope this representation would work, because it could simplify the code, and compartmentalizes things. Part of what motivates this is that I've been looking at the downstream reset code this week. It contains a large number of register offset definitions identical to what's used for the clock driver. The reset driver uses exactly the same registers as the clock driver does. Downstream they are separate drivers, but the clock driver exports a shared spinlock for both drivers to use. These really need to be incorporated into the same driver for upstream. The clock code defines four distinct "units" (a term I'll use from here on; there might be a better name): MPMU Main Power Management Unit APMU Application Power Management Unit APBC APB Clock APBS APB Spare The reset code defines some of those, but doesn't use APBS. It also defines three more: APBC2 Another APB Clock RCPU Real-time CPU? RCPU2 Another Real-time CPU Each of these "units" has a distinct I/O memory region that contains registers that manage the clocks and reset signals. I suggest a single "k1-clocks" device be created, which has access to all of the I/O address ranges. And then within the DT node for that device there is a sub-node for the clocks, and another sub-node for the resets. Each of these uses 2 cells for addressing. The first indicates which "unit" contains the clock or reset registers, and the second indicates the clock or reset line implemented by that unit. I might not have this exactly right, but below I show some DTS code that I hope demonstrates what I mean. Could this approach, or something close, work? -Alex /* SpacemiT clock/reset unit numbers */ #define SPACEMIT_CRST_RCPU 0 #define SPACEMIT_CRST_RCPU2 1 #define SPACEMIT_CRST_APBC 2 /* . . . */ #define SPACEMIT_CRST_APBC2 6 /* SpacemiT RCPU unit reset IDs */ #define SPACEMIT_RCPU_HDMIAUDIO 0 #define SPACEMIT_RCPU_CAN 1 /* . . . */ #define SPACEMIT_RCPU_UART1 6 /* SpacemiT RCPU2 unit reset IDs */ #define SPACEMIT_RCPU2_PWM0 0 #define SPACEMIT_RCPU2_PWM1 1 /* . . . */ #define SPACEMIT_RCPU2_PWM9 9 /* SpacemiT APBC unit reset/clock IDs */ #define SPACEMIT_APBC_UART1 0 #define SPACEMIT_APBC_UART2 1 #define SPACEMIT_APBC_GPIO 2 /* . . . */ #define SPACEMIT_APBC_TWSI0 27 /* APBC reset/clock IDs */ #define SPACEMIT_APBC_UART1 0 #define SPACEMIT_APBC_UART2 1 #define SPACEMIT_APBC_GPIO 2 /* . . . */ clocks { compatible = "spacemit,k1-clocks"; reg = <0x0 0xc0880000 0x0 0x2050>, <0x0 0xc0888000 0x0 0x30>, <0x0 0xd4015000 0x0 0x1000>, <0x0 0xd4050000 0x0 0x209c>, <0x0 0xd4090000 0x0 0x1000>, <0x0 0xd4282800 0x0 0x400>, <0x0 0xf0610000 0x0 0x20>; reg-names = "rcpu", "rcpu2", "apbc", "mpmu", "apbs", "apmu", "apbc2"; /* * The two reset cells indicate: * unit number (e.g. SPACEMIT_CRST_RCPU) * reset within that unit (e.g. SPACEMIT_RCPU_CAN) */ k1_reset: spacemit-k1-reset { #reset-cells = <2>; }; /* * The two clock cells indicate: * unit number (e.g. SPACEMIT_CRST_APBC) * clock within that unit (e.g. SPACEMIT_APBC_UART1) */ k1_clock: spacemit-k1-clock { #clock-cells = <2>; }; /* . . . */ }; /* . . . */ uart8: serial@d4017700 { compatible = "spacemit,k1-uart", "intel,xscale-uart"; reg = <0x0 0xd4017700 0x0 0x100>; interrupts = <50>; clocks = <&k1_clock SPACEMIT_CRST_APBC SPACEMIT_APBC_UART8>; <&k1_clock SPACEMIT_CRST_APBC SPACEMIT_APBC_UART8_BUS>; clock-names = "core", "bus"; reg-shift = <2>; reg-io-width = <4>; resets = <&k1_reset SPACEMIT_CRST_APBC SPACEMIT_APBC_UART8>; status = "disabled"; }; ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-21 23:40 ` Alex Elder @ 2025-02-22 9:59 ` Krzysztof Kozlowski 2025-02-22 10:48 ` Haylen Chu 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-02-22 9:59 UTC (permalink / raw) To: Alex Elder, Haylen Chu Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu On 22/02/2025 00:40, Alex Elder wrote: > I have a general proposal on how to represent this, but I'd > like to know whether it makes sense. It might be what Krzysztof > is suggesting, but in any case, I hope this representation would > work, because it could simplify the code, and compartmentalizes > things. > > Part of what motivates this is that I've been looking at the > downstream reset code this week. It contains a large number of > register offset definitions identical to what's used for the > clock driver. The reset driver uses exactly the same registers > as the clock driver does. Downstream they are separate drivers, > but the clock driver exports a shared spinlock for both drivers > to use. > > These really need to be incorporated into the same driver for > upstream. Why? First, it is not related to the topic here at all. You can design drivers as you wish and still nothing to do with discussion about binding. Second, different subsystems justify different drivers and Linux handles this well already. No need for custom spinlock - regmap already does it. > > The clock code defines four distinct "units" (a term I'll use > from here on; there might be a better name): > MPMU Main Power Management Unit > APMU Application Power Management Unit > APBC APB Clock > APBS APB Spare > > The reset code defines some of those, but doesn't use APBS. > It also defines three more: > APBC2 Another APB Clock > RCPU Real-time CPU? > RCPU2 Another Real-time CPU > > Each of these "units" has a distinct I/O memory region that > contains registers that manage the clocks and reset signals. So there are children - mpmu, apmu, apbclock, apbspare, apbclock2, rcpu 1+2? But previous statements were saying these are intermixed? " I'll make APMU/MPMU act as a whole device" > > I suggest a single "k1-clocks" device be created, which has For four devices? Or for one device? No, it's again going to wrong direction. I already said: "You need to define what is the device here. Don't create fake nodes ust for your drivers. If registers are interleaved and manual says "this is block APMU/MPMU" then you have one device, so one node with 'reg'." So what is the device here? Can you people actually answer? > access to all of the I/O address ranges. And then within > the DT node for that device there is a sub-node for the Uh, confusing. You said there is one device for all the clocks, so if there is one device so also one device node. No children. Maybe you have more devices but none of you is explaining the hardware that way. Mixing talk about drivers is really not helping. > Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-22 9:59 ` Krzysztof Kozlowski @ 2025-02-22 10:48 ` Haylen Chu 2025-02-22 11:50 ` Krzysztof Kozlowski 0 siblings, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-02-22 10:48 UTC (permalink / raw) To: Krzysztof Kozlowski, Alex Elder Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu On Sat, Feb 22, 2025 at 10:59:09AM +0100, Krzysztof Kozlowski wrote: > On 22/02/2025 00:40, Alex Elder wrote: > > I have a general proposal on how to represent this, but I'd > > like to know whether it makes sense. It might be what Krzysztof > > is suggesting, but in any case, I hope this representation would > > work, because it could simplify the code, and compartmentalizes > > things. > > > > Part of what motivates this is that I've been looking at the > > downstream reset code this week. It contains a large number of > > register offset definitions identical to what's used for the > > clock driver. The reset driver uses exactly the same registers > > as the clock driver does. Downstream they are separate drivers, > > but the clock driver exports a shared spinlock for both drivers > > to use. > > > > These really need to be incorporated into the same driver for > > upstream. > > Why? First, it is not related to the topic here at all. You can design > drivers as you wish and still nothing to do with discussion about binding. > Second, different subsystems justify different drivers and Linux handles > this well already. No need for custom spinlock - regmap already does it. > > > > > > The clock code defines four distinct "units" (a term I'll use > > from here on; there might be a better name): > > MPMU Main Power Management Unit > > APMU Application Power Management Unit > > APBC APB Clock > > APBS APB Spare > > > > The reset code defines some of those, but doesn't use APBS. > > It also defines three more: > > APBC2 Another APB Clock > > RCPU Real-time CPU? > > RCPU2 Another Real-time CPU > > > > Each of these "units" has a distinct I/O memory region that > > contains registers that manage the clocks and reset signals. > > So there are children - mpmu, apmu, apbclock, apbspare, apbclock2, rcpu > 1+2? But previous statements were saying these are intermixed? > > " I'll make APMU/MPMU act as a whole device" My reply seems somehow misleading. The statement means I will merge the children with the syscon into one devicetree node, which applies for both APMU and MPMU. I wasn't going to say that APMU and MPMU are intermixed. As Alex said, all these units have their own distinct and separate MMIO regions. > > > > I suggest a single "k1-clocks" device be created, which has > > For four devices? Or for one device? By Alex's example, I think he means a device node taking all these distinct MMIO regions as resource. clock { compatible = "spacemit,k1-clocks"; reg = <0x0 0xc0880000 0x0 0x2050>, <0x0 0xc0888000 0x0 0x30>, <0x0 0xd4015000 0x0 0x1000>, <0x0 0xd4050000 0x0 0x209c>, <0x0 0xd4090000 0x0 0x1000>, <0x0 0xd4282800 0x0 0x400>, <0x0 0xf0610000 0x0 0x20>; reg-names = "rcpu", "rcpu2", "apbc", "mpmu", "apbs", "apmu", "apbc2"; /* ... */ }; > No, it's again going to wrong direction. I already said: > > "You need to define what is the device here. Don't create fake nodes ust > for your drivers. If registers are interleaved and manual says "this is > block APMU/MPMU" then you have one device, so one node with 'reg'." > > So what is the device here? Can you people actually answer? > I'm not sure about the apbc2, rcpu and rcpu2 regions; they aren't related to the thread, either. For APBC, MPMU, APBS and APMU, I'm pretty sure they're standalone blocks with distinct and separate MMIO regions, this could be confirmed by the address mapping[1]. > > > access to all of the I/O address ranges. And then within > > the DT node for that device there is a sub-node for the > > Uh, confusing. You said there is one device for all the clocks, so if > there is one device so also one device node. No children. > > Maybe you have more devices but none of you is explaining the hardware > that way. Mixing talk about drivers is really not helping. > > > > > Best regards, > Krzysztof Best regards, Haylen Chu [1]: https://developer.spacemit.com/documentation?token=LzJyw97BCipK1dkUygrcbT0NnMg ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-22 10:48 ` Haylen Chu @ 2025-02-22 11:50 ` Krzysztof Kozlowski 2025-02-24 10:17 ` Haylen Chu 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-02-22 11:50 UTC (permalink / raw) To: Haylen Chu, Alex Elder Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu On 22/02/2025 11:48, Haylen Chu wrote: > On Sat, Feb 22, 2025 at 10:59:09AM +0100, Krzysztof Kozlowski wrote: >> On 22/02/2025 00:40, Alex Elder wrote: >>> I have a general proposal on how to represent this, but I'd >>> like to know whether it makes sense. It might be what Krzysztof >>> is suggesting, but in any case, I hope this representation would >>> work, because it could simplify the code, and compartmentalizes >>> things. >>> >>> Part of what motivates this is that I've been looking at the >>> downstream reset code this week. It contains a large number of >>> register offset definitions identical to what's used for the >>> clock driver. The reset driver uses exactly the same registers >>> as the clock driver does. Downstream they are separate drivers, >>> but the clock driver exports a shared spinlock for both drivers >>> to use. >>> >>> These really need to be incorporated into the same driver for >>> upstream. >> >> Why? First, it is not related to the topic here at all. You can design >> drivers as you wish and still nothing to do with discussion about binding. >> Second, different subsystems justify different drivers and Linux handles >> this well already. No need for custom spinlock - regmap already does it. >> >> >>> >>> The clock code defines four distinct "units" (a term I'll use >>> from here on; there might be a better name): >>> MPMU Main Power Management Unit >>> APMU Application Power Management Unit >>> APBC APB Clock >>> APBS APB Spare >>> >>> The reset code defines some of those, but doesn't use APBS. >>> It also defines three more: >>> APBC2 Another APB Clock >>> RCPU Real-time CPU? >>> RCPU2 Another Real-time CPU >>> >>> Each of these "units" has a distinct I/O memory region that >>> contains registers that manage the clocks and reset signals. >> >> So there are children - mpmu, apmu, apbclock, apbspare, apbclock2, rcpu >> 1+2? But previous statements were saying these are intermixed? >> >> " I'll make APMU/MPMU act as a whole device" > > My reply seems somehow misleading. The statement means I will merge the > children with the syscon into one devicetree node, which applies for > both APMU and MPMU. I wasn't going to say that APMU and MPMU are > intermixed. > > As Alex said, all these units have their own distinct and separate MMIO > regions. > >>> >>> I suggest a single "k1-clocks" device be created, which has >> >> For four devices? Or for one device? > > By Alex's example, I think he means a device node taking all these > distinct MMIO regions as resource. You still do not answer about the hardware: how many devices is there? > > clock { > compatible = "spacemit,k1-clocks"; > > reg = <0x0 0xc0880000 0x0 0x2050>, > <0x0 0xc0888000 0x0 0x30>, > <0x0 0xd4015000 0x0 0x1000>, > <0x0 0xd4050000 0x0 0x209c>, > <0x0 0xd4090000 0x0 0x1000>, > <0x0 0xd4282800 0x0 0x400>, > <0x0 0xf0610000 0x0 0x20>; > reg-names = "rcpu", > "rcpu2", > "apbc", > "mpmu", > "apbs", > "apmu", > "apbc2"; > > /* ... */ > }; > >> No, it's again going to wrong direction. I already said: >> >> "You need to define what is the device here. Don't create fake nodes ust >> for your drivers. If registers are interleaved and manual says "this is >> block APMU/MPMU" then you have one device, so one node with 'reg'." >> >> So what is the device here? Can you people actually answer? >> > > I'm not sure about the apbc2, rcpu and rcpu2 regions; they aren't > related to the thread, either. For APBC, MPMU, APBS and APMU, I'm pretty > sure they're standalone blocks with distinct and separate MMIO regions, > this could be confirmed by the address mapping[1]. They were brought here to discuss for some reason. Long discussions, long emails, unrelated topics like hardware or different devices - all this is not making it easier for me to understand. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-22 11:50 ` Krzysztof Kozlowski @ 2025-02-24 10:17 ` Haylen Chu 2025-02-25 8:12 ` Krzysztof Kozlowski 0 siblings, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-02-24 10:17 UTC (permalink / raw) To: Krzysztof Kozlowski, Alex Elder Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu On Sat, Feb 22, 2025 at 12:50:13PM +0100, Krzysztof Kozlowski wrote: > On 22/02/2025 11:48, Haylen Chu wrote: > > On Sat, Feb 22, 2025 at 10:59:09AM +0100, Krzysztof Kozlowski wrote: > >> On 22/02/2025 00:40, Alex Elder wrote: > >>> I have a general proposal on how to represent this, but I'd > >>> like to know whether it makes sense. It might be what Krzysztof > >>> is suggesting, but in any case, I hope this representation would > >>> work, because it could simplify the code, and compartmentalizes > >>> things. > >>> > >>> Part of what motivates this is that I've been looking at the > >>> downstream reset code this week. It contains a large number of > >>> register offset definitions identical to what's used for the > >>> clock driver. The reset driver uses exactly the same registers > >>> as the clock driver does. Downstream they are separate drivers, > >>> but the clock driver exports a shared spinlock for both drivers > >>> to use. > >>> > >>> These really need to be incorporated into the same driver for > >>> upstream. > >> > >> Why? First, it is not related to the topic here at all. You can design > >> drivers as you wish and still nothing to do with discussion about binding. > >> Second, different subsystems justify different drivers and Linux handles > >> this well already. No need for custom spinlock - regmap already does it. > >> > >> > >>> > >>> The clock code defines four distinct "units" (a term I'll use > >>> from here on; there might be a better name): > >>> MPMU Main Power Management Unit > >>> APMU Application Power Management Unit > >>> APBC APB Clock > >>> APBS APB Spare > >>> > >>> The reset code defines some of those, but doesn't use APBS. > >>> It also defines three more: > >>> APBC2 Another APB Clock > >>> RCPU Real-time CPU? > >>> RCPU2 Another Real-time CPU > >>> > >>> Each of these "units" has a distinct I/O memory region that > >>> contains registers that manage the clocks and reset signals. > >> > >> So there are children - mpmu, apmu, apbclock, apbspare, apbclock2, rcpu > >> 1+2? But previous statements were saying these are intermixed? > >> > >> " I'll make APMU/MPMU act as a whole device" > > > > My reply seems somehow misleading. The statement means I will merge the > > children with the syscon into one devicetree node, which applies for > > both APMU and MPMU. I wasn't going to say that APMU and MPMU are > > intermixed. > > > > As Alex said, all these units have their own distinct and separate MMIO > > regions. > > > >>> > >>> I suggest a single "k1-clocks" device be created, which has > >> > >> For four devices? Or for one device? > > > > By Alex's example, I think he means a device node taking all these > > distinct MMIO regions as resource. > > > You still do not answer about the hardware: how many devices is there? In my understanding, the series covers four devices, APBC, APMU, MPMU and APBS, each comes with its separate MMIO region and is clearly described in the datasheet. I stated this in the later part of the reply, > > For APBC, MPMU, APBS and APMU, I'm pretty > > sure they're standalone blocks with distinct and separate MMIO regions, > > this could be confirmed by the address mapping[1]. Thus I don't agree on Alex's solution, since it creates fake devices not mentioned by the datasheet (spacemit,k1-clocks and all its children in the example devicetree). > > > > clock { > > compatible = "spacemit,k1-clocks"; > > > > reg = <0x0 0xc0880000 0x0 0x2050>, > > <0x0 0xc0888000 0x0 0x30>, > > <0x0 0xd4015000 0x0 0x1000>, > > <0x0 0xd4050000 0x0 0x209c>, > > <0x0 0xd4090000 0x0 0x1000>, > > <0x0 0xd4282800 0x0 0x400>, > > <0x0 0xf0610000 0x0 0x20>; > > reg-names = "rcpu", > > "rcpu2", > > "apbc", > > "mpmu", > > "apbs", > > "apmu", > > "apbc2"; > > > > /* ... */ > > }; > > > >> No, it's again going to wrong direction. I already said: > >> > >> "You need to define what is the device here. Don't create fake nodes ust > >> for your drivers. If registers are interleaved and manual says "this is > >> block APMU/MPMU" then you have one device, so one node with 'reg'." > >> > >> So what is the device here? Can you people actually answer? > >> > > > > I'm not sure about the apbc2, rcpu and rcpu2 regions; they aren't > > related to the thread, either. For APBC, MPMU, APBS and APMU, I'm pretty > > sure they're standalone blocks with distinct and separate MMIO regions, > > this could be confirmed by the address mapping[1]. > > They were brought here to discuss for some reason. Long discussions, > long emails, unrelated topics like hardware or different devices - all > this is not making it easier for me to understand. > > Best regards, > Krzysztof By the way, I made a summary on the hardware covered by this series in one of my earlier reply[1]. Could you please comment further on my proposal[2] according it, or pointing out anything that's unclear or missing? It will be helpful for things to improve. Thanks, Haylen Chu [1]: https://lore.kernel.org/all/Z7m2oNXbwJ06KtLQ@ketchup/ [2]: https://lore.kernel.org/all/Z7BTVu10EKHMqOnJ@ketchup/ ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-24 10:17 ` Haylen Chu @ 2025-02-25 8:12 ` Krzysztof Kozlowski 2025-02-25 21:14 ` Alex Elder 0 siblings, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-02-25 8:12 UTC (permalink / raw) To: Haylen Chu, Alex Elder Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu On 24/02/2025 11:17, Haylen Chu wrote: > On Sat, Feb 22, 2025 at 12:50:13PM +0100, Krzysztof Kozlowski wrote: >> On 22/02/2025 11:48, Haylen Chu wrote: >>> On Sat, Feb 22, 2025 at 10:59:09AM +0100, Krzysztof Kozlowski wrote: >>>> On 22/02/2025 00:40, Alex Elder wrote: >>>>> I have a general proposal on how to represent this, but I'd >>>>> like to know whether it makes sense. It might be what Krzysztof >>>>> is suggesting, but in any case, I hope this representation would >>>>> work, because it could simplify the code, and compartmentalizes >>>>> things. >>>>> >>>>> Part of what motivates this is that I've been looking at the >>>>> downstream reset code this week. It contains a large number of >>>>> register offset definitions identical to what's used for the >>>>> clock driver. The reset driver uses exactly the same registers >>>>> as the clock driver does. Downstream they are separate drivers, >>>>> but the clock driver exports a shared spinlock for both drivers >>>>> to use. >>>>> >>>>> These really need to be incorporated into the same driver for >>>>> upstream. >>>> >>>> Why? First, it is not related to the topic here at all. You can design >>>> drivers as you wish and still nothing to do with discussion about binding. >>>> Second, different subsystems justify different drivers and Linux handles >>>> this well already. No need for custom spinlock - regmap already does it. >>>> >>>> >>>>> >>>>> The clock code defines four distinct "units" (a term I'll use >>>>> from here on; there might be a better name): >>>>> MPMU Main Power Management Unit >>>>> APMU Application Power Management Unit >>>>> APBC APB Clock >>>>> APBS APB Spare >>>>> >>>>> The reset code defines some of those, but doesn't use APBS. >>>>> It also defines three more: >>>>> APBC2 Another APB Clock >>>>> RCPU Real-time CPU? >>>>> RCPU2 Another Real-time CPU >>>>> >>>>> Each of these "units" has a distinct I/O memory region that >>>>> contains registers that manage the clocks and reset signals. >>>> >>>> So there are children - mpmu, apmu, apbclock, apbspare, apbclock2, rcpu >>>> 1+2? But previous statements were saying these are intermixed? >>>> >>>> " I'll make APMU/MPMU act as a whole device" >>> >>> My reply seems somehow misleading. The statement means I will merge the >>> children with the syscon into one devicetree node, which applies for >>> both APMU and MPMU. I wasn't going to say that APMU and MPMU are >>> intermixed. >>> >>> As Alex said, all these units have their own distinct and separate MMIO >>> regions. >>> >>>>> >>>>> I suggest a single "k1-clocks" device be created, which has >>>> >>>> For four devices? Or for one device? >>> >>> By Alex's example, I think he means a device node taking all these >>> distinct MMIO regions as resource. >> >> >> You still do not answer about the hardware: how many devices is there? > > In my understanding, the series covers four devices, APBC, APMU, MPMU > and APBS, each comes with its separate MMIO region and is clearly > described in the datasheet. I stated this in the later part of the > reply, Ack > >>> For APBC, MPMU, APBS and APMU, I'm pretty >>> sure they're standalone blocks with distinct and separate MMIO regions, >>> this could be confirmed by the address mapping[1]. > > Thus I don't agree on Alex's solution, since it creates fake devices not > mentioned by the datasheet (spacemit,k1-clocks and all its children in > the example devicetree). Ack > >>> >>> clock { >>> compatible = "spacemit,k1-clocks"; >>> >>> reg = <0x0 0xc0880000 0x0 0x2050>, >>> <0x0 0xc0888000 0x0 0x30>, >>> <0x0 0xd4015000 0x0 0x1000>, >>> <0x0 0xd4050000 0x0 0x209c>, >>> <0x0 0xd4090000 0x0 0x1000>, >>> <0x0 0xd4282800 0x0 0x400>, >>> <0x0 0xf0610000 0x0 0x20>; >>> reg-names = "rcpu", >>> "rcpu2", >>> "apbc", >>> "mpmu", >>> "apbs", >>> "apmu", >>> "apbc2"; >>> >>> /* ... */ >>> }; >>> >>>> No, it's again going to wrong direction. I already said: >>>> >>>> "You need to define what is the device here. Don't create fake nodes ust >>>> for your drivers. If registers are interleaved and manual says "this is >>>> block APMU/MPMU" then you have one device, so one node with 'reg'." >>>> >>>> So what is the device here? Can you people actually answer? >>>> >>> >>> I'm not sure about the apbc2, rcpu and rcpu2 regions; they aren't >>> related to the thread, either. For APBC, MPMU, APBS and APMU, I'm pretty >>> sure they're standalone blocks with distinct and separate MMIO regions, >>> this could be confirmed by the address mapping[1]. >> >> They were brought here to discuss for some reason. Long discussions, >> long emails, unrelated topics like hardware or different devices - all >> this is not making it easier for me to understand. >> >> Best regards, >> Krzysztof > > By the way, I made a summary on the hardware covered by this series in > one of my earlier reply[1]. Could you please comment further on my > proposal[2] according it, or pointing out anything that's unclear or > missing? It will be helpful for things to improve. Thanks, it looks good. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-25 8:12 ` Krzysztof Kozlowski @ 2025-02-25 21:14 ` Alex Elder 0 siblings, 0 replies; 31+ messages in thread From: Alex Elder @ 2025-02-25 21:14 UTC (permalink / raw) To: Krzysztof Kozlowski, Haylen Chu Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Guodong Xu On 2/25/25 2:12 AM, Krzysztof Kozlowski wrote: >>> They were brought here to discuss for some reason. Long discussions, >>> long emails, unrelated topics like hardware or different devices - all >>> this is not making it easier for me to understand. >>> >>> Best regards, >>> Krzysztof >> By the way, I made a summary on the hardware covered by this series in >> one of my earlier reply[1]. Could you please comment further on my >> proposal[2] according it, or pointing out anything that's unclear or >> missing? It will be helpful for things to improve. > Thanks, it looks good. Sorry about my earlier message; I hadn't reviewed the discussion carefully enough, and what I said didn't help. It seems like Haylen is on the right track now, and I'll wait for the next version of the series to comment again. -Alex ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-15 8:41 ` Haylen Chu 2025-02-21 23:40 ` Alex Elder @ 2025-02-22 9:52 ` Krzysztof Kozlowski 2025-02-22 11:36 ` Haylen Chu 1 sibling, 1 reply; 31+ messages in thread From: Krzysztof Kozlowski @ 2025-02-22 9:52 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On 15/02/2025 09:41, Haylen Chu wrote: > >>> }; >>> >>> For the other two clock controllers (APBS and APBC), syscons are really >>> unnecessary and it's simple to fold them. >> >> >> I don't follow. Do we talk about children or syscon compatible? > > APBS region contains only clock (PLL) bits and APBC region contains only > reset and clock bits, so I was thinking about dropping the syscon nodes > and changing their compatible to spacemit,k1-plls and > spacemit,k1-cru-apbc. > > In summary, my plan is, > > - For MPMU, APMU and APBC region, keep the binding in soc/spacemit. > They'll be reset, clock and power controllers, with compatible > "spacemit,k1-syscon-*". > - For APBS region, write a new binding clock/spacemit,k1-plls, as it > contains only PLL-related bits. It acts as clock controller. > - All split children will be eliminated, there'll be only four device > nodes, one for each region, matching the datasheet. > - Put all clock-related binding definition of SpacemiT K1 in > dt-bindings/clock/spacemit,k1-ccu.h > > Is it fine for you? > That did not explain hardware to me. You assume that some way, maybe through magical crystal ball, I know your hardware and will tell you what to do. No. I have dozens of other patches in my inbox. It's you who should explain the hardware in simple, concise way so we can judge whether DT description is correct. Again: define what is the actual device, what is its address space, what are its possible *separate* and *distinctive* children. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon 2025-02-22 9:52 ` Krzysztof Kozlowski @ 2025-02-22 11:36 ` Haylen Chu 0 siblings, 0 replies; 31+ messages in thread From: Haylen Chu @ 2025-02-22 11:36 UTC (permalink / raw) To: Krzysztof Kozlowski Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan, linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Sat, Feb 22, 2025 at 10:52:02AM +0100, Krzysztof Kozlowski wrote: > On 15/02/2025 09:41, Haylen Chu wrote: > > > >>> }; > >>> > >>> For the other two clock controllers (APBS and APBC), syscons are really > >>> unnecessary and it's simple to fold them. > >> > >> > >> I don't follow. Do we talk about children or syscon compatible? > > > > APBS region contains only clock (PLL) bits and APBC region contains only > > reset and clock bits, so I was thinking about dropping the syscon nodes > > and changing their compatible to spacemit,k1-plls and > > spacemit,k1-cru-apbc. > > > > In summary, my plan is, > > > > - For MPMU, APMU and APBC region, keep the binding in soc/spacemit. > > They'll be reset, clock and power controllers, with compatible > > "spacemit,k1-syscon-*". > > - For APBS region, write a new binding clock/spacemit,k1-plls, as it > > contains only PLL-related bits. It acts as clock controller. > > - All split children will be eliminated, there'll be only four device > > nodes, one for each region, matching the datasheet. > > - Put all clock-related binding definition of SpacemiT K1 in > > dt-bindings/clock/spacemit,k1-ccu.h > > > > Is it fine for you? > > > > That did not explain hardware to me. Sorry if my replies haven't made things clear. I'm goint to make a (hopefully) more clear conclusion, > You assume that some way, maybe > through magical crystal ball, I know your hardware and will tell you > what to do. > > No. > > I have dozens of other patches in my inbox. It's you who should explain > the hardware in simple, concise way so we can judge whether DT > description is correct. > > Again: define what is the actual device, what is its address space, what > are its possible *separate* and *distinctive* children. The series covers four seperate blocks, - Application Power Manage Unit, APMU - Main Power Manage Unit, MPMU - APB Bus Clock Unit, APBC - APB Spare, APBS they're clearly separate blocks and have their own distinct, separate address spaces, confirmed by the Address Mapping section in the TRM[1]. These four blocks provide hardware bits for three purposes: power management, reset signals and clocks. Not every block is capable of all the three functionalities, - APMU, MPMU: power, reset, clock - APBC: clock, reset - APBS: clock Reset and clock bits, if present, always stay in the same register. Power management bits stay in others. These two types of registers interleave if present in the same block (APMU and MPMU case). These blocks have no child: power, clock and reset definitions differ from block to block, no reusable nodes could be split from them. Hope this conclusion will help the reviewing. Please tell if something is unclear. > > Best regards, > Krzysztof Thanks, Haylen Chu [1]: https://developer.spacemit.com/documentation?token=LzJyw97BCipK1dkUygrcbT0NnMg ^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-01-03 21:56 [PATCH v4 0/4] Add clock controller support for SpacemiT K1 Haylen Chu 2025-01-03 21:56 ` [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC Haylen Chu 2025-01-03 21:56 ` [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu @ 2025-01-03 21:56 ` Haylen Chu 2025-01-16 5:25 ` Samuel Holland 2025-02-14 4:04 ` Alex Elder 2025-01-03 21:56 ` [PATCH v4 4/4] riscv: dts: spacemit: Add clock controller for K1 Haylen Chu 3 siblings, 2 replies; 31+ messages in thread From: Haylen Chu @ 2025-01-03 21:56 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, 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 is managed by several independent controllers in different SoC parts (APBC, APBS and etc.), thus different compatible strings are added to distinguish them. Some controllers may share IO region with reset controller and other low speed peripherals like watchdog, so all register operations are done through regmap to avoid competition. 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 | 1747 +++++++++++++++++++++++++++++ drivers/clk/spacemit/ccu_common.h | 51 + drivers/clk/spacemit/ccu_ddn.c | 140 +++ drivers/clk/spacemit/ccu_ddn.h | 84 ++ drivers/clk/spacemit/ccu_mix.c | 304 +++++ drivers/clk/spacemit/ccu_mix.h | 309 +++++ drivers/clk/spacemit/ccu_pll.c | 189 ++++ drivers/clk/spacemit/ccu_pll.h | 80 ++ 12 files changed, 2931 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 299bc678ed1b..fa8b5e8f2926 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -498,6 +498,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 fb8878a5d7d9..e82ece2d0095 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -129,6 +129,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..6fb0a12ec261 --- /dev/null +++ b/drivers/clk/spacemit/ccu-k1.c @@ -0,0 +1,1747 @@ +// 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/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 */ +/* pll1 */ +#define APB_SPARE1_REG 0x100 +#define APB_SPARE2_REG 0x104 +#define APB_SPARE3_REG 0x108 +/* pll2 */ +#define APB_SPARE7_REG 0x118 +#define APB_SPARE8_REG 0x11c +#define APB_SPARE9_REG 0x120 +/* pll3 */ +#define APB_SPARE10_REG 0x124 +#define APB_SPARE11_REG 0x128 +#define APB_SPARE12_REG 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_CCI550_CLK_CTRL 0x300 +#define APMU_CPU_C0_CLK_CTRL 0x38C +#define APMU_CPU_C1_CLK_CTRL 0x390 +#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_USB_CLK_RES_CTRL 0x5c +#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, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), +}; + +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), +}; + +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), +}; + +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); +static CCU_PLL_DEFINE(pll2, "pll2", pll2_rate_tbl, + APB_SPARE7_REG, APB_SPARE8_REG, APB_SPARE9_REG, + MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE); +static CCU_PLL_DEFINE(pll3, "pll3", pll3_rate_tbl, + APB_SPARE10_REG, APB_SPARE11_REG, APB_SPARE12_REG, + MPMU_POSR, POSR_PLL3_LOCK, 0); + +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(1), BIT(1), 0, 2, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(2), BIT(2), 0, 3, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(3), BIT(3), 0, 4, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(4), BIT(4), 0, 5, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(5), BIT(5), 0, 6, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(6), BIT(6), 0, 7, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(7), BIT(7), 0, 8, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d11_223p4, "pll1_d11_223p4", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(15), BIT(15), 0, 11, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d13_189, "pll1_d13_189", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(16), BIT(16), 0, 13, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d23_106p8, "pll1_d23_106p8", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(20), BIT(20), 0, 23, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d64_38p4, "pll1_d64_38p4", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(0), BIT(0), 0, 64, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_aud_245p7, "pll1_aud_245p7", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(10), BIT(10), 0, 10, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_aud_24p5, "pll1_aud_24p5", CCU_PARENT_HW(pll1), + APB_SPARE2_REG, + BIT(11), BIT(11), 0, 100, 1, 0); + +static CCU_GATE_FACTOR_DEFINE(pll2_d1, "pll2_d1", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(0), BIT(0), 0, 1, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d2, "pll2_d2", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(1), BIT(1), 0, 2, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d3, "pll2_d3", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(2), BIT(2), 0, 3, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d4, "pll2_d4", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(3), BIT(3), 0, 4, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d5, "pll2_d5", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(4), BIT(4), 0, 5, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d6, "pll2_d6", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(5), BIT(5), 0, 6, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d7, "pll2_d7", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(6), BIT(6), 0, 7, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll2_d8, "pll2_d8", CCU_PARENT_HW(pll2), + APB_SPARE8_REG, + BIT(7), BIT(7), 0, 8, 1, 0); + +static CCU_GATE_FACTOR_DEFINE(pll3_d1, "pll3_d1", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(0), BIT(0), 0, 1, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d2, "pll3_d2", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(1), BIT(1), 0, 2, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d3, "pll3_d3", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(2), BIT(2), 0, 3, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d4, "pll3_d4", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(3), BIT(3), 0, 4, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d5, "pll3_d5", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(4), BIT(4), 0, 5, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d6, "pll3_d6", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(5), BIT(5), 0, 6, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d7, "pll3_d7", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(6), BIT(6), 0, 7, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll3_d8, "pll3_d8", CCU_PARENT_HW(pll3), + APB_SPARE11_REG, + BIT(7), BIT(7), 0, 8, 1, 0); + +static CCU_FACTOR_DEFINE(pll3_20, "pll3_20", CCU_PARENT_HW(pll3_d8), 20, 1); +static CCU_FACTOR_DEFINE(pll3_40, "pll3_40", CCU_PARENT_HW(pll3_d8), 10, 1); +static CCU_FACTOR_DEFINE(pll3_80, "pll3_80", CCU_PARENT_HW(pll3_d8), 5, 1); + +/* APBS clocks end */ + +/* MPMU clocks start */ +static CCU_GATE_DEFINE(pll1_d8_307p2, "pll1_d8_307p2", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(13), BIT(13), 0, 0); +static CCU_FACTOR_DEFINE(pll1_d32_76p8, "pll1_d32_76p8", CCU_PARENT_HW(pll1_d8_307p2), + 4, 1); +static CCU_FACTOR_DEFINE(pll1_d40_61p44, "pll1_d40_61p44", CCU_PARENT_HW(pll1_d8_307p2), + 5, 1); +static CCU_FACTOR_DEFINE(pll1_d16_153p6, "pll1_d16_153p6", CCU_PARENT_HW(pll1_d8), + 2, 1); +static CCU_GATE_FACTOR_DEFINE(pll1_d24_102p4, "pll1_d24_102p4", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(12), BIT(12), 0, 3, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2, "pll1_d48_51p2", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(7), BIT(7), 0, 6, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2_ap, "pll1_d48_51p2_ap", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(11), BIT(11), 0, 6, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_m3d128_57p6, "pll1_m3d128_57p6", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(8), BIT(8), 0, 16, 3, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d96_25p6, "pll1_d96_25p6", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(4), BIT(4), 0, 12, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8, "pll1_d192_12p8", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(3), BIT(3), 0, 24, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8_wdt, "pll1_d192_12p8_wdt", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(19), BIT(19), 0x0, 24, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d384_6p4, "pll1_d384_6p4", CCU_PARENT_HW(pll1_d8), + MPMU_ACGR, + BIT(2), BIT(2), 0, 48, 1, 0); +static CCU_FACTOR_DEFINE(pll1_d768_3p2, "pll1_d768_3p2", CCU_PARENT_HW(pll1_d384_6p4), + 2, 1); +static CCU_FACTOR_DEFINE(pll1_d1536_1p6, "pll1_d1536_1p6", CCU_PARENT_HW(pll1_d384_6p4), + 4, 1); +static CCU_FACTOR_DEFINE(pll1_d3072_0p8, "pll1_d3072_0p8", CCU_PARENT_HW(pll1_d384_6p4), + 8, 1); + +static CCU_FACTOR_DEFINE(pll1_d7_351p08, "pll1_d7_351p08", CCU_PARENT_HW(pll1_d7), + 1, 1); + +static CCU_GATE_DEFINE(pll1_d6_409p6, "pll1_d6_409p6", CCU_PARENT_HW(pll1_d6), + MPMU_ACGR, + BIT(0), BIT(0), 0, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d12_204p8, "pll1_d12_204p8", CCU_PARENT_HW(pll1_d6), + MPMU_ACGR, + BIT(5), BIT(5), 0, 2, 1, 0); + +static CCU_GATE_DEFINE(pll1_d5_491p52, "pll1_d5_491p52", CCU_PARENT_HW(pll1_d5), + MPMU_ACGR, + BIT(21), BIT(21), 0, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d10_245p76, "pll1_d10_245p76", CCU_PARENT_HW(pll1_d5), + MPMU_ACGR, + BIT(18), BIT(18), 0, 2, 1, 0); + +static CCU_GATE_DEFINE(pll1_d4_614p4, "pll1_d4_614p4", CCU_PARENT_HW(pll1_d4), + MPMU_ACGR, + BIT(15), BIT(15), 0, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d52_47p26, "pll1_d52_47p26", CCU_PARENT_HW(pll1_d4), + MPMU_ACGR, + BIT(10), BIT(10), 0, 13, 1, 0); +static CCU_GATE_FACTOR_DEFINE(pll1_d78_31p5, "pll1_d78_31p5", CCU_PARENT_HW(pll1_d4), + MPMU_ACGR, + BIT(6), BIT(6), 0, 39, 2, 0); + +static CCU_GATE_DEFINE(pll1_d3_819p2, "pll1_d3_819p2", CCU_PARENT_HW(pll1_d3), + MPMU_ACGR, + BIT(14), BIT(14), 0, 0); + +static CCU_GATE_DEFINE(pll1_d2_1228p8, "pll1_d2_1228p8", CCU_PARENT_HW(pll1_d2), + MPMU_ACGR, + BIT(16), BIT(16), 0, 0); + +static struct ccu_ddn_info uart_ddn_mask_info = { + .factor = 2, + .num_mask = 0x1fff, + .den_mask = 0x1fff, + .num_shift = 16, + .den_shift = 0, +}; +static struct ccu_ddn_tbl slow_uart1_tbl[] = { + { .num = 125, .den = 24 }, +}; +static struct ccu_ddn_tbl slow_uart2_tbl[] = { + { .num = 6144, .den = 960 }, +}; +static CCU_GATE_DEFINE(slow_uart, "slow_uart", CCU_PARENT_NAME(osc), + MPMU_ACGR, + BIT(1), BIT(1), 0, CLK_IGNORE_UNUSED); +static CCU_DDN_DEFINE(slow_uart1_14p74, "slow_uart1_14p74", pll1_d16_153p6, + &uart_ddn_mask_info, slow_uart1_tbl, + MPMU_SUCCR, 0); +static CCU_DDN_DEFINE(slow_uart2_48, "slow_uart2_48", pll1_d4_614p4, + &uart_ddn_mask_info, slow_uart2_tbl, + MPMU_SUCCR_1, 0); + +static CCU_GATE_DEFINE(wdt_clk, "wdt_clk", CCU_PARENT_HW(pll1_d96_25p6), + MPMU_WDTPCR, + BIT(1), BIT(1), 0x0, + 0); + +static CCU_GATE_DEFINE(ripc_clk, "ripc_clk", CCU_PARENT_NAME(vctcxo_24m), + MPMU_RIPCCR, + 0x3, 0x3, 0x0, + 0); + +static CCU_GATE_FACTOR_DEFINE(i2s_sysclk, "i2s_sysclk", CCU_PARENT_HW(pll1_d16_153p6), + MPMU_ISCCR, + BIT(31), BIT(31), 0x0, 50, 1, + 0); +static CCU_GATE_FACTOR_DEFINE(i2s_bclk, "i2s_bclk", CCU_PARENT_HW(i2s_sysclk), + MPMU_ISCCR, + BIT(29), BIT(29), 0x0, 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_clk", apb_parents, + MPMU_APBCSCR, + 0, 2, + 0); + +static CCU_GATE_DEFINE(wdt_bus_clk, "wdt_bus_clk", CCU_PARENT_HW(apb_clk), + MPMU_WDTPCR, + BIT(2), BIT(2), 0x0, + 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, "uart0_clk", uart_clk_parents, + APBC_UART1_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + CLK_IS_CRITICAL); +static CCU_MUX_GATE_DEFINE(uart2_clk, "uart2_clk", uart_clk_parents, + APBC_UART2_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart3_clk, "uart3_clk", uart_clk_parents, + APBC_UART3_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart4_clk, "uart4_clk", uart_clk_parents, + APBC_UART4_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart5_clk, "uart5_clk", uart_clk_parents, + APBC_UART5_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart6_clk, "uart6_clk", uart_clk_parents, + APBC_UART6_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart7_clk, "uart7_clk", uart_clk_parents, + APBC_UART7_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart8_clk, "uart8_clk", uart_clk_parents, + APBC_UART8_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(uart9_clk, "uart9_clk", uart_clk_parents, + APBC_UART9_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); + +static CCU_GATE_DEFINE(gpio_clk, "gpio_clk", CCU_PARENT_NAME(vctcxo_24m), + APBC_GPIO_CLK_RST, + BIT(1), BIT(1), 0x0, + 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, "pwm0_clk", pwm_parents, + APBC_PWM0_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm1_clk, "pwm1_clk", pwm_parents, + APBC_PWM1_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm2_clk, "pwm2_clk", pwm_parents, + APBC_PWM2_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm3_clk, "pwm3_clk", pwm_parents, + APBC_PWM3_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm4_clk, "pwm4_clk", pwm_parents, + APBC_PWM4_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm5_clk, "pwm5_clk", pwm_parents, + APBC_PWM5_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm6_clk, "pwm6_clk", pwm_parents, + APBC_PWM6_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm7_clk, "pwm7_clk", pwm_parents, + APBC_PWM7_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm8_clk, "pwm8_clk", pwm_parents, + APBC_PWM8_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm9_clk, "pwm9_clk", pwm_parents, + APBC_PWM9_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm10_clk, "pwm10_clk", pwm_parents, + APBC_PWM10_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm11_clk, "pwm11_clk", pwm_parents, + APBC_PWM11_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm12_clk, "pwm12_clk", pwm_parents, + APBC_PWM12_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm13_clk, "pwm13_clk", pwm_parents, + APBC_PWM13_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm14_clk, "pwm14_clk", pwm_parents, + APBC_PWM14_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm15_clk, "pwm15_clk", pwm_parents, + APBC_PWM15_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm16_clk, "pwm16_clk", pwm_parents, + APBC_PWM16_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm17_clk, "pwm17_clk", pwm_parents, + APBC_PWM17_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm18_clk, "pwm18_clk", pwm_parents, + APBC_PWM18_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(pwm19_clk, "pwm19_clk", pwm_parents, + APBC_PWM19_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 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, "ssp3_clk", ssp_parents, + APBC_SSP3_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); + +static CCU_GATE_DEFINE(rtc_clk, "rtc_clk", CCU_PARENT_NAME(osc), + APBC_RTC_CLK_RST, + 0x82, 0x82, 0x0, + 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, "twsi0_clk", twsi_parents, + APBC_TWSI0_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi1_clk, "twsi1_clk", twsi_parents, + APBC_TWSI1_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi2_clk, "twsi2_clk", twsi_parents, + APBC_TWSI2_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi4_clk, "twsi4_clk", twsi_parents, + APBC_TWSI4_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi5_clk, "twsi5_clk", twsi_parents, + APBC_TWSI5_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi6_clk, "twsi6_clk", twsi_parents, + APBC_TWSI6_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi7_clk, "twsi7_clk", twsi_parents, + APBC_TWSI7_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(twsi8_clk, "twsi8_clk", twsi_parents, + APBC_TWSI8_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 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, "timers1_clk", timer_parents, + APBC_TIMERS1_CLK_RST, + 4, 3, 0x3, 0x3, 0x0, + 0); +static CCU_MUX_GATE_DEFINE(timers2_clk, "timers2_clk", timer_parents, + APBC_TIMERS2_CLK_RST, + 4, 3, 0x3, 0x3, 0x0, + 0); + +static CCU_GATE_DEFINE(aib_clk, "aib_clk", CCU_PARENT_NAME(vctcxo_24m), + APBC_AIB_CLK_RST, + BIT(1), BIT(1), 0x0, + 0); + +static CCU_GATE_DEFINE(onewire_clk, "onewire_clk", CCU_PARENT_NAME(vctcxo_24m), + APBC_ONEWIRE_CLK_RST, + BIT(1), BIT(1), 0x0, + 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, "sspa0_clk", sspa_parents, + APBC_SSPA0_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_MUX_GATE_DEFINE(sspa1_clk, "sspa1_clk", sspa_parents, + APBC_SSPA1_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(dro_clk, "dro_clk", CCU_PARENT_HW(apb_clk), + APBC_DRO_CLK_RST, + BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(ir_clk, "ir_clk", CCU_PARENT_HW(apb_clk), + APBC_IR_CLK_RST, + BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(tsen_clk, "tsen_clk", CCU_PARENT_HW(apb_clk), + APBC_TSEN_CLK_RST, + BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(ipc_ap2aud_clk, "ipc_ap2aud_clk", CCU_PARENT_HW(apb_clk), + APBC_IPC_AP2AUD_CLK_RST, + BIT(1), BIT(1), 0x0, + 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, "can0_clk", can_parents, + APBC_CAN0_CLK_RST, + 4, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(can0_bus_clk, "can0_bus_clk", CCU_PARENT_NAME(vctcxo_24m), + APBC_CAN0_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(uart0_bus_clk, "uart0_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART1_CLK_RST, + BIT(0), BIT(0), 0x0, + CLK_IS_CRITICAL); +static CCU_GATE_DEFINE(uart2_bus_clk, "uart2_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART2_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart3_bus_clk, "uart3_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART3_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart4_bus_clk, "uart4_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART4_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart5_bus_clk, "uart5_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART5_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart6_bus_clk, "uart6_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART6_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart7_bus_clk, "uart7_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART7_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart8_bus_clk, "uart8_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART8_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(uart9_bus_clk, "uart9_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_UART9_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(gpio_bus_clk, "gpio_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_GPIO_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(pwm0_bus_clk, "pwm0_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM0_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm1_bus_clk, "pwm1_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM1_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm2_bus_clk, "pwm2_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM2_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm3_bus_clk, "pwm3_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM3_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm4_bus_clk, "pwm4_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM4_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm5_bus_clk, "pwm5_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM5_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm6_bus_clk, "pwm6_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM6_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm7_bus_clk, "pwm7_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM7_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm8_bus_clk, "pwm8_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM8_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm9_bus_clk, "pwm9_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM9_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm10_bus_clk, "pwm10_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM10_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm11_bus_clk, "pwm11_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM11_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm12_bus_clk, "pwm12_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM12_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm13_bus_clk, "pwm13_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM13_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm14_bus_clk, "pwm14_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM14_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm15_bus_clk, "pwm15_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM15_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm16_bus_clk, "pwm16_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM16_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm17_bus_clk, "pwm17_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM17_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm18_bus_clk, "pwm18_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM18_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(pwm19_bus_clk, "pwm19_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_PWM19_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(ssp3_bus_clk, "ssp3_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_SSP3_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(rtc_bus_clk, "rtc_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_RTC_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(twsi0_bus_clk, "twsi0_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI0_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi1_bus_clk, "twsi1_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI1_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi2_bus_clk, "twsi2_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI2_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi4_bus_clk, "twsi4_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI4_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi5_bus_clk, "twsi5_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI5_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi6_bus_clk, "twsi6_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI6_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi7_bus_clk, "twsi7_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI7_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(twsi8_bus_clk, "twsi8_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TWSI8_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(timers1_bus_clk, "timers1_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TIMERS1_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(timers2_bus_clk, "timers2_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TIMERS2_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(aib_bus_clk, "aib_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_AIB_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(onewire_bus_clk, "onewire_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_ONEWIRE_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(sspa0_bus_clk, "sspa0_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_SSPA0_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(sspa1_bus_clk, "sspa1_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_SSPA1_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(tsen_bus_clk, "tsen_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_TSEN_CLK_RST, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(ipc_ap2aud_bus_clk, "ipc_ap2aud_bus_clk", CCU_PARENT_HW(apb_clk), + APBC_IPC_AP2AUD_CLK_RST, + BIT(0), BIT(0), 0x0, + 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", 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", 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", 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_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, "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, "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", 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_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, "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_clk", jpg_parents, + APMU_JPG_CLK_RES_CTRL, + 5, 3, BIT(15), + 2, 3, BIT(1), BIT(1), 0x0, + 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_clk", ccic2phy_parents, + APMU_CSI_CCIC2_CLK_RES_CTRL, + 7, 1, BIT(5), BIT(5), 0x0, + 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_clk", ccic3phy_parents, + APMU_CSI_CCIC2_CLK_RES_CTRL, + 31, 1, BIT(30), BIT(30), 0x0, + 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_clk", csi_parents, + APMU_CSI_CCIC2_CLK_RES_CTRL, + 20, 3, BIT(15), + 16, 3, BIT(4), BIT(4), 0x0, + 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, "camm0_clk", camm_parents, + APMU_CSI_CCIC2_CLK_RES_CTRL, + 23, 4, 8, 2, + BIT(28), BIT(28), 0x0, + 0); +static CCU_DIV_MUX_GATE_DEFINE(camm1_clk, "camm1_clk", camm_parents, + APMU_CSI_CCIC2_CLK_RES_CTRL, + 23, 4, 8, 2, BIT(6), BIT(6), 0x0, + 0); +static CCU_DIV_MUX_GATE_DEFINE(camm2_clk, "camm2_clk", camm_parents, + APMU_CSI_CCIC2_CLK_RES_CTRL, + 23, 4, 8, 2, BIT(3), BIT(3), 0x0, + 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_clk", isp_cpp_parents, + APMU_ISP_CLK_RES_CTRL, + 24, 2, 26, 1, BIT(28), BIT(28), 0x0, + 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_clk", isp_bus_parents, + APMU_ISP_CLK_RES_CTRL, + 18, 3, BIT(23), + 21, 2, BIT(17), BIT(17), 0x0, + 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_clk", isp_parents, + APMU_ISP_CLK_RES_CTRL, + 4, 3, BIT(7), + 8, 2, BIT(1), BIT(1), 0x0, + 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_DIV2_FC_MUX_GATE_DEFINE(dpu_mclk, "dpu_mclk", dpumclk_parents, + APMU_LCD_CLK_RES_CTRL2, + APMU_LCD_CLK_RES_CTRL1, + 1, 4, BIT(29), + 5, 3, BIT(0), BIT(0), 0x0, + 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, "dpu_esc_clk", dpuesc_parents, + APMU_LCD_CLK_RES_CTRL1, + 0, 2, BIT(2), BIT(2), 0x0, + 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_d8), + CCU_PARENT_HW(pll2_d8), +}; +static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, "dpu_bit_clk", dpubit_parents, + APMU_LCD_CLK_RES_CTRL1, + 17, 3, BIT(31), + 20, 3, BIT(16), BIT(16), 0x0, + 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_DIV2_FC_MUX_GATE_DEFINE(dpu_pxclk, "dpu_pxclk", dpupx_parents, + APMU_LCD_CLK_RES_CTRL2, + APMU_LCD_CLK_RES_CTRL1, + 17, 4, BIT(30), + 21, 3, BIT(16), BIT(16), 0x0, + 0); + +static CCU_GATE_DEFINE(dpu_hclk, "dpu_hclk", CCU_PARENT_HW(pmua_aclk), + APMU_LCD_CLK_RES_CTRL1, + BIT(5), BIT(5), 0x0, + 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_clk", dpu_spi_parents, + APMU_LCD_SPI_CLK_RES_CTRL, + 8, 3, BIT(7), + 12, 3, BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(dpu_spi_hbus_clk, "dpu_spi_hbus_clk", CCU_PARENT_HW(pmua_aclk), + APMU_LCD_SPI_CLK_RES_CTRL, + BIT(3), BIT(3), 0x0, + 0); +static CCU_GATE_DEFINE(dpu_spi_bus_clk, "dpu_spi_bus_clk", CCU_PARENT_HW(pmua_aclk), + APMU_LCD_SPI_CLK_RES_CTRL, + BIT(5), BIT(5), 0x0, + 0); +static CCU_GATE_DEFINE(dpu_spi_aclk, "dpu_spi_aclk", CCU_PARENT_HW(pmua_aclk), + APMU_LCD_SPI_CLK_RES_CTRL, + BIT(6), BIT(6), 0x0, + 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_clk", v2d_parents, + APMU_LCD_CLK_RES_CTRL1, + 9, 3, BIT(28), + 12, 2, BIT(8), BIT(8), 0x0, + 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_clk", ccic_4x_parents, + APMU_CCIC_CLK_RES_CTRL, + 18, 3, BIT(15), + 23, 2, BIT(4), BIT(4), 0x0, + 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_clk", ccic1phy_parents, + APMU_CCIC_CLK_RES_CTRL, + 7, 1, BIT(5), BIT(5), 0x0, + 0); + +static CCU_GATE_DEFINE(sdh_axi_aclk, "sdh_axi_aclk", CCU_PARENT_HW(pmua_aclk), + APMU_SDH0_CLK_RES_CTRL, + BIT(3), BIT(3), 0x0, + 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, "sdh0_clk", sdh01_parents, + APMU_SDH0_CLK_RES_CTRL, + 8, 3, BIT(11), + 5, 3, BIT(4), BIT(4), 0x0, + 0); +static CCU_DIV_FC_MUX_GATE_DEFINE(sdh1_clk, "sdh1_clk", sdh01_parents, + APMU_SDH1_CLK_RES_CTRL, + 8, 3, BIT(11), + 5, 3, BIT(4), BIT(4), 0x0, + 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_clk", sdh2_parents, + APMU_SDH2_CLK_RES_CTRL, + 8, 3, BIT(11), + 5, 3, BIT(4), BIT(4), 0x0, + 0); + +static CCU_GATE_DEFINE(usb_axi_clk, "usb_axi_clk", CCU_PARENT_HW(pmua_aclk), + APMU_USB_CLK_RES_CTRL, + BIT(1), BIT(1), 0x0, + 0); +static CCU_GATE_DEFINE(usb_p1_aclk, "usb_p1_aclk", CCU_PARENT_HW(pmua_aclk), + APMU_USB_CLK_RES_CTRL, + BIT(5), BIT(5), 0x0, + 0); +static CCU_GATE_DEFINE(usb30_clk, "usb30_clk", CCU_PARENT_HW(pmua_aclk), + APMU_USB_CLK_RES_CTRL, + BIT(8), BIT(8), 0x0, + 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_clk", qspi_parents, + APMU_QSPI_CLK_RES_CTRL, + 9, 3, BIT(12), + 6, 3, BIT(4), BIT(4), 0x0, + 0); +static CCU_GATE_DEFINE(qspi_bus_clk, "qspi_bus_clk", CCU_PARENT_HW(pmua_aclk), + APMU_QSPI_CLK_RES_CTRL, + BIT(3), BIT(3), 0x0, + 0); +static CCU_GATE_DEFINE(dma_clk, "dma_clk", CCU_PARENT_HW(pmua_aclk), + APMU_DMA_CLK_RES_CTRL, + BIT(3), BIT(3), 0x0, + 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_clk", aes_parents, + APMU_AES_CLK_RES_CTRL, + 6, 1, BIT(5), BIT(5), 0x0, + 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_clk", vpu_parents, + APMU_VPU_CLK_RES_CTRL, + 13, 3, BIT(21), + 10, 3, + BIT(3), BIT(3), 0x0, + 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_clk", gpu_parents, + APMU_GPU_CLK_RES_CTRL, + 12, 3, BIT(15), + 18, 3, + BIT(4), BIT(4), 0x0, + 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_clk", emmc_parents, + APMU_PMUA_EM_CLK_RES_CTRL, + 8, 3, BIT(11), + 6, 2, + BIT(4), BIT(4), 0x0, + 0); +static CCU_DIV_GATE_DEFINE(emmc_x_clk, "emmc_x_clk", CCU_PARENT_HW(pll1_d2_1228p8), + APMU_PMUA_EM_CLK_RES_CTRL, + 12, 3, BIT(15), BIT(15), 0x0, + 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_clk", audio_parents, + APMU_AUDIO_CLK_RES_CTRL, + 4, 3, BIT(15), + 7, 3, + BIT(12), BIT(12), 0x0, + 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_mclk", hdmi_parents, + APMU_HDMI_CLK_RES_CTRL, + 1, 4, BIT(29), + 5, 3, + BIT(0), BIT(0), 0x0, + 0); + +static CCU_GATE_DEFINE(pcie0_clk, "pcie0_clk", CCU_PARENT_HW(pmua_aclk), + APMU_PCIE_CLK_RES_CTRL_0, + 0x7, 0x7, 0x0, + 0); +static CCU_GATE_DEFINE(pcie1_clk, "pcie1_clk", CCU_PARENT_HW(pmua_aclk), + APMU_PCIE_CLK_RES_CTRL_1, + 0x7, 0x7, 0x0, + 0); +static CCU_GATE_DEFINE(pcie2_clk, "pcie2_clk", CCU_PARENT_HW(pmua_aclk), + APMU_PCIE_CLK_RES_CTRL_2, + 0x7, 0x7, 0x0, + 0); + +static CCU_GATE_DEFINE(emac0_bus_clk, "emac0_bus_clk", CCU_PARENT_HW(pmua_aclk), + APMU_EMAC0_CLK_RES_CTRL, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(emac0_ptp_clk, "emac0_ptp_clk", CCU_PARENT_HW(pll2_d6), + APMU_EMAC0_CLK_RES_CTRL, + BIT(15), BIT(15), 0x0, + 0); +static CCU_GATE_DEFINE(emac1_bus_clk, "emac1_bus_clk", CCU_PARENT_HW(pmua_aclk), + APMU_EMAC1_CLK_RES_CTRL, + BIT(0), BIT(0), 0x0, + 0); +static CCU_GATE_DEFINE(emac1_ptp_clk, "emac1_ptp_clk", CCU_PARENT_HW(pll2_d6), + APMU_EMAC1_CLK_RES_CTRL, + BIT(15), BIT(15), 0x0, + 0); + +static CCU_GATE_DEFINE(emmc_bus_clk, "emmc_bus_clk", CCU_PARENT_HW(pmua_aclk), + APMU_PMUA_EM_CLK_RES_CTRL, + BIT(3), BIT(3), 0, + 0); +/* APMU clocks end */ + +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { + .hws = { + [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, + + }, + .num = CLK_APBS_NUM, +}; + +static struct clk_hw_onecell_data k1_ccu_mpmu_clks = { + .hws = { + [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_351] = &pll1_d7_351p08.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, + }, + .num = CLK_MPMU_NUM, +}; + +static struct clk_hw_onecell_data k1_ccu_apbc_clks = { + .hws = { + [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, + }, + .num = CLK_APBC_NUM, +}; + +static struct clk_hw_onecell_data k1_ccu_apmu_clks = { + .hws = { + [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] = &pcie0_clk.common.hw, + [CLK_PCIE1] = &pcie1_clk.common.hw, + [CLK_PCIE2] = &pcie2_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, + }, + .num = CLK_APMU_NUM +}; + +struct spacemit_ccu_data { + struct clk_hw_onecell_data *hw_clks; + bool need_pll_lock; +}; + +struct spacemit_ccu_priv { + const struct spacemit_ccu_data *data; + struct regmap *base; + struct regmap *lock_base; +}; + +static int spacemit_ccu_register(struct device *dev, + struct spacemit_ccu_priv *priv) +{ + const struct spacemit_ccu_data *data = priv->data; + int i, ret; + + for (i = 0; i < data->hw_clks->num; i++) { + struct clk_hw *hw = data->hw_clks->hws[i]; + struct ccu_common *common; + const char *name; + + if (!hw) + continue; + + common = hw_to_ccu_common(hw); + name = hw->init->name; + + common->base = priv->base; + common->lock_base = priv->lock_base; + + ret = devm_clk_hw_register(dev, hw); + if (ret) { + dev_err(dev, "Cannot register clock %d - %s\n", + i, name); + return ret; + } + } + + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, + data->hw_clks); +} + +static int k1_ccu_probe(struct platform_device *pdev) +{ + const struct spacemit_ccu_data *data; + struct regmap *base_map, *lock_map = NULL; + struct device *dev = &pdev->dev; + struct spacemit_ccu_priv *priv; + struct device_node *parent; + int ret; + + data = of_device_get_match_data(dev); + if (WARN_ON(!data)) + return -EINVAL; + + parent = of_get_parent(dev->of_node); + base_map = syscon_node_to_regmap(parent); + of_node_put(parent); + + if (IS_ERR(base_map)) + return dev_err_probe(dev, PTR_ERR(base_map), + "failed to get regmap\n"); + + if (data->need_pll_lock) { + lock_map = syscon_regmap_lookup_by_phandle(dev->of_node, + "spacemit,mpmu"); + if (IS_ERR(lock_map)) + return dev_err_probe(dev, PTR_ERR(lock_map), + "failed to get lock regmap\n"); + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = data; + priv->base = base_map; + priv->lock_base = lock_map; + + ret = spacemit_ccu_register(dev, priv); + if (ret) + return dev_err_probe(dev, ret, "failed to register clocks\n"); + + return 0; +} + +static const struct spacemit_ccu_data k1_ccu_apbs_data = { + .need_pll_lock = true, + .hw_clks = &k1_ccu_apbs_clks, +}; + +static const struct spacemit_ccu_data k1_ccu_mpmu_data = { + .need_pll_lock = false, + .hw_clks = &k1_ccu_mpmu_clks, +}; + +static const struct spacemit_ccu_data k1_ccu_apbc_data = { + .need_pll_lock = false, + .hw_clks = &k1_ccu_apbc_clks, +}; + +static const struct spacemit_ccu_data k1_ccu_apmu_data = { + .need_pll_lock = false, + .hw_clks = &k1_ccu_apmu_clks, +}; + +static const struct of_device_id of_k1_ccu_match[] = { + { + .compatible = "spacemit,k1-ccu-apbs", + .data = &k1_ccu_apbs_data, + }, + { + .compatible = "spacemit,k1-ccu-mpmu", + .data = &k1_ccu_mpmu_data, + }, + { + .compatible = "spacemit,k1-ccu-apbc", + .data = &k1_ccu_apbc_data, + }, + { + .compatible = "spacemit,k1-ccu-apmu", + .data = &k1_ccu_apmu_data, + }, + { } +}; +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..242461ee592f --- /dev/null +++ b/drivers/clk/spacemit/ccu_common.h @@ -0,0 +1,51 @@ +/* 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 *base; + struct regmap *lock_base; + + union { + struct { + u32 reg_ctrl; + u32 reg_sel; + u32 reg_fc; + u32 fc; + }; + struct { + u32 reg_swcr1; + u32 reg_swcr2; + u32 reg_swcr3; + }; + }; + + unsigned long flags; + const char *name; + const char * const *parent_names; + int num_parents; + + 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)->base, (c)->reg_##reg, val) +#define ccu_write(reg, c, val) regmap_write((c)->base, (c)->reg_##reg, val) +#define ccu_update(reg, c, mask, val) \ + regmap_update_bits((c)->base, (c)->reg_##reg, mask, val) +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \ + regmap_read_poll_timeout_atomic((c)->base, (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..1df555888ecb --- /dev/null +++ b/drivers/clk/spacemit/ccu_ddn.c @@ -0,0 +1,140 @@ +// 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 "ccu_ddn.h" + +/* + * It is M/N clock + * + * Fout from synthesizer can be given from two equations: + * numerator/denominator = Fin / (Fout * factor) + */ +static void ccu_ddn_disable(struct clk_hw *hw) +{ + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); + struct ccu_common *common = &ddn->common; + + ccu_update(sel, common, ddn->gate, 0); +} + +static int ccu_ddn_enable(struct clk_hw *hw) +{ + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); + struct ccu_common *common = &ddn->common; + + ccu_update(sel, common, ddn->gate, ddn->gate); + + return 0; +} + +static int ccu_ddn_is_enabled(struct clk_hw *hw) +{ + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); + struct ccu_common *common = &ddn->common; + u32 tmp; + + ccu_read(sel, common, &tmp); + + return tmp & ddn->gate; +} + +static long clk_ddn_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *prate) +{ + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); + struct ccu_ddn_config *params = &ddn->ddn; + unsigned long rate = 0, prev_rate; + unsigned long result; + int i; + + for (i = 0; i < params->tbl_size; i++) { + prev_rate = rate; + rate = (*prate * params->tbl[i].den) / + (params->tbl[i].num * params->info->factor); + if (rate > drate) + break; + } + + if ((i == 0) || (i == params->tbl_size)) { + result = rate; + } else { + if ((drate - prev_rate) > (rate - drate)) + result = rate; + else + result = prev_rate; + } + + return result; +} + +static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); + struct ccu_ddn_config *params = &ddn->ddn; + unsigned int val, num, den; + unsigned long rate; + + ccu_read(ctrl, &ddn->common, &val); + + num = (val >> params->info->num_shift) & params->info->num_mask; + den = (val >> params->info->den_shift) & params->info->den_mask; + + if (!den) + return 0; + + rate = (parent_rate * den) / (num * params->info->factor); + + return rate; +} + +/* Configures new clock rate*/ +static int clk_ddn_set_rate(struct clk_hw *hw, unsigned long drate, + unsigned long prate) +{ + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); + struct ccu_ddn_config *params = &ddn->ddn; + struct ccu_ddn_info *info = params->info; + unsigned long rate = 0; + int i; + + for (i = 0; i < params->tbl_size; i++) { + rate = (prate * params->tbl[i].den) / + (params->tbl[i].num * info->factor); + + if (rate > drate) + break; + } + + if (i > 0) + i--; + + ccu_update(ctrl, &ddn->common, + info->num_mask | info->den_mask, + (params->tbl[i].num << info->num_shift) | + (params->tbl[i].den << info->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, +}; + +const struct clk_ops spacemit_ccu_ddn_gate_ops = { + .disable = ccu_ddn_disable, + .enable = ccu_ddn_enable, + .is_enabled = ccu_ddn_is_enabled, + .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..6fa658bcd50a --- /dev/null +++ b/drivers/clk/spacemit/ccu_ddn.h @@ -0,0 +1,84 @@ +/* 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_tbl { + unsigned int num; + unsigned int den; +}; + +struct ccu_ddn_info { + unsigned int factor; + unsigned int num_mask; + unsigned int den_mask; + unsigned int num_shift; + unsigned int den_shift; +}; + +struct ccu_ddn_config { + struct ccu_ddn_info *info; + struct ccu_ddn_tbl *tbl; + u32 tbl_size; +}; + +struct ccu_ddn { + struct ccu_ddn_config ddn; + struct ccu_common common; + u32 gate; +}; + +#define CCU_DDN_CONFIG(_info, _table) \ + { \ + .info = (struct ccu_ddn_info *)_info, \ + .tbl = (struct ccu_ddn_tbl *)&_table, \ + .tbl_size = ARRAY_SIZE(_table), \ + } + +#define CCU_DDN_INIT(_name, _parent, _ops, _flags) \ + CLK_HW_INIT_HW(_name, &_parent.common.hw, &_ops, _flags) + +#define CCU_DDN_DEFINE(_struct, _name, _parent, _info, _table, \ + _reg_ctrl, _flags) \ + struct ccu_ddn _struct = { \ + .ddn = CCU_DDN_CONFIG(_info, _table), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .hw.init = CCU_DDN_INIT(_name, _parent, \ + spacemit_ccu_ddn_ops, \ + _flags), \ + } \ + } + +#define CCU_DDN_GATE_DEFINE(_struct, _name, _parent, _info, _table, \ + _reg_ddn, _reg_gate, _gate_mask, _flags) \ + struct ccu_ddn _struct = { \ + .ddn = CCU_DDN_CONFIG(_info, _table), \ + .common = { \ + .reg_ctrl = _reg_ddn, \ + .reg_sel = _reg_gate, \ + .hw.init = CCU_DDN_INIT(_name, _parent, \ + &spacemit_ccu_ddn_gate_ops, \ + _flags), \ + } \ + .gate = _gate_mask, \ + } + +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, spacemit_ccu_ddn_gate_ops; + +#endif diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c new file mode 100644 index 000000000000..b46eeb59faea --- /dev/null +++ b/drivers/clk/spacemit/ccu_mix.c @@ -0,0 +1,304 @@ +// 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; + struct ccu_gate_config *gate = mix->gate; + + ccu_update(ctrl, common, gate->gate_mask, gate->val_disable); +} + +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; + u32 val_enable, mask; + u32 tmp; + + val_enable = gate->val_enable; + mask = gate->gate_mask; + + ccu_update(ctrl, common, mask, val_enable); + + return ccu_poll(ctrl, common, tmp, (tmp & mask) == val_enable, + 10, MIX_TIMEOUT); +} + +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; + struct ccu_gate_config *gate = mix->gate; + u32 tmp; + + ccu_read(ctrl, common, &tmp); + + return (tmp & gate->gate_mask) == gate->val_enable; +} + +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, ®); + + val = reg >> div->shift; + val &= (1 << div->width) - 1; + + val = divider_recalc_rate(hw, parent_rate, val, div->table, + div->flags, 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); + struct ccu_common *common = &mix->common; + struct ccu_div_config *div = mix->div; + u32 div_max = div ? 1 << div->width : 1; + unsigned long best_rate = 0; + + for (int i = 0; i < common->num_parents; i++) { + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); + + if (!parent) + continue; + + unsigned long 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, ®); + + parent = reg >> mux->shift; + parent &= (1 << mux->width) - 1; + + if (mux->table) { + int num_parents = clk_hw_get_num_parents(&common->hw); + int i; + + for (i = 0; i < num_parents; i++) + if (mux->table[i] == parent) + return i; + } + + 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; + + if (mux->table) + index = mux->table[index]; + + 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..97a3a099b42f --- /dev/null +++ b/drivers/clk/spacemit/ccu_mix.h @@ -0,0 +1,309 @@ +/* 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 gate_mask; + u32 val_enable; + u32 val_disable; + u32 flags; +}; + +struct ccu_factor_config { + u32 div; + u32 mul; +}; + +struct ccu_mux_config { + const u8 *table; + u32 flags; + u8 shift; + u8 width; +}; + +struct ccu_div_config { + struct clk_div_table *table; + u32 max; + u32 offset; + u32 flags; + 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(_gate_mask, _val_enable, _val_disable, _flags) \ + (&(struct ccu_gate_config) { \ + .gate_mask = _gate_mask, \ + .val_enable = _val_enable, \ + .val_disable = _val_disable, \ + .flags = _flags, \ + }) + +#define CCU_FACTOR_INIT(_div, _mul) \ + (&(struct ccu_factor_config) { \ + .div = _div, \ + .mul = _mul, \ + }) + + +#define CCU_MUX_INIT(_shift, _width, _table, _flags) \ + (&(struct ccu_mux_config) { \ + .shift = _shift, \ + .width = _width, \ + .table = _table, \ + .flags = _flags, \ + }) + +#define CCU_DIV_INIT(_shift, _width, _table, _flags) \ + (&(struct ccu_div_config) { \ + .shift = _shift, \ + .width = _width, \ + .flags = _flags, \ + .table = _table, \ + }) + +#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(_struct, _name, _parent, _reg, _gate_mask, \ + _val_enable, _val_disable, _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .name = _name, \ + .num_parents = 1, \ + .hw.init = CCU_MIX_INITHW(_name, _parent, \ + spacemit_ccu_gate_ops, _flags), \ + } \ +} + +#define CCU_FACTOR_DEFINE(_struct, _name, _parent, _div, _mul) \ +struct ccu_mix _struct = { \ + .factor = CCU_FACTOR_INIT(_div, _mul), \ + .common = { \ + .name = _name, \ + .num_parents = 1, \ + .hw.init = CCU_MIX_INITHW(_name, _parent, \ + spacemit_ccu_factor_ops, 0), \ + } \ +} + +#define CCU_MUX_DEFINE(_struct, _name, _parents, _reg, _shift, _width, \ + _flags) \ +struct ccu_mix _struct = { \ + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_mux_ops, _flags),\ + } \ +} + +#define CCU_DIV_DEFINE(_struct, _name, _parent, _reg, _shift, _width, \ + _flags) \ +struct ccu_mix _struct = { \ + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .name = _name, \ + .num_parents = 1, \ + .hw.init = CCU_MIX_INITHW(_name, _parent, \ + spacemit_ccu_div_ops, _flags) \ + } \ +} + +#define CCU_GATE_FACTOR_DEFINE(_struct, _name, _parent, _reg, \ + _gate_mask, _val_enable, _val_disable, \ + _div, _mul, _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .factor = CCU_FACTOR_INIT(_div, _mul), \ + .common = { \ + .reg_ctrl = _reg, \ + .name = _name, \ + .num_parents = 1, \ + .hw.init = CCU_MIX_INITHW(_name, _parent, \ + spacemit_ccu_gate_factor_ops, _flags) \ + } \ +} + +#define CCU_MUX_GATE_DEFINE(_struct, _name, _parents, _reg, _shift, \ + _width, _gate_mask, _val_enable, \ + _val_disable, _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_mux_gate_ops, \ + _flags), \ + } \ +} + +#define CCU_DIV_GATE_DEFINE(_struct, _name, _parent, _reg, _shift, \ + _width, _gate_mask, _val_enable, \ + _val_disable, _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg, \ + .name = _name, \ + .num_parents = 1, \ + .hw.init = CCU_MIX_INITHW(_name, _parent, \ + spacemit_ccu_div_gate_ops, _flags), \ + } \ +} + +#define CCU_DIV_MUX_GATE_DEFINE(_struct, _name, _parents, _reg_ctrl, \ + _mshift, _mwidth, _muxshift, _muxwidth, \ + _gate_mask, _val_enable, _val_disable, \ + _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_div_mux_gate_ops,\ + _flags), \ + }, \ +} + +#define CCU_DIV2_FC_MUX_GATE_DEFINE(_struct, _name, _parents, \ + _reg_ctrl, _reg_fc, _mshift, \ + _mwidth, _fc, _muxshift, _muxwidth, \ + _gate_mask, _val_enable, \ + _val_disable, _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .reg_fc = _reg_fc, \ + .fc = _fc, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_div_mux_gate_ops,\ + _flags), \ + }, \ +} + +#define CCU_DIV_FC_MUX_GATE_DEFINE(_struct, _name, _parents, _reg_ctrl, \ + _mshift, _mwidth, _fc, _muxshift, \ + _muxwidth, _gate_mask, _val_enable, \ + _val_disable, _flags) \ +struct ccu_mix _struct = { \ + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ + _val_disable, 0), \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .reg_fc = _reg_ctrl, \ + .fc = _fc, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_div_mux_gate_ops,\ + _flags), \ + }, \ +} + +#define CCU_DIV_FC_MUX_DEFINE(_struct, _name, _parents, _reg_ctrl, \ + _mshift, _mwidth, _fc, _muxshift, \ + _muxwidth, _flags) \ +struct ccu_mix _struct = { \ + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .reg_fc = _reg_ctrl, \ + .fc = _fc, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ + spacemit_ccu_div_mux_ops, \ + _flags), \ + }, \ +} + +#define CCU_MUX_FC_DEFINE(_struct, _name, _parents, _reg_ctrl, _fc, \ + _muxshift, _muxwidth, _flags) \ +struct ccu_mix _struct = { \ + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ + .common = { \ + .reg_ctrl = _reg_ctrl, \ + .reg_fc = _reg_ctrl, \ + .fc = _fc, \ + .name = _name, \ + .num_parents = ARRAY_SIZE(_parents), \ + .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..81b929ca1c5c --- /dev/null +++ b/drivers/clk/spacemit/ccu_pll.c @@ -0,0 +1,189 @@ +// 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_MIN_FREQ 600000000 +#define PLL_MAX_FREQ 3400000000 +#define PLL_DELAY_TIME 3000 + +#define PLL_SWCR1_REG5_OFF 0 +#define PLL_SWCR1_REG5_MASK GENMASK(7, 0) +#define PLL_SWCR1_REG6_OFF 8 +#define PLL_SWCR1_REG6_MASK GENMASK(15, 8) +#define PLL_SWCR1_REG7_OFF 16 +#define PLL_SWCR1_REG7_MASK GENMASK(23, 16) +#define PLL_SWCR1_REG8_OFF 24 +#define PLL_SWCR1_REG8_MASK GENMASK(31, 24) + +#define PLL_SWCR2_DIVn_EN(n) BIT(n + 1) +#define PLL_SWCR2_ATEST_EN BIT(12) +#define PLL_SWCR2_CKTEST_EN BIT(13) +#define PLL_SWCR2_DTEST_EN BIT(14) + +#define PLL_SWCR3_DIV_FRC_OFF 0 +#define PLL_SWCR3_DIV_FRC_MASK GENMASK(23, 0) +#define PLL_SWCR3_DIV_INT_OFF 24 +#define PLL_SWCR3_DIV_INT_MASK GENMASK(30, 24) +#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) +{ + unsigned int reg5, reg6, reg7, reg8, size, i; + unsigned int div_int, div_frc; + struct ccu_pll_rate_tbl *freq_pll_regs_table; + struct ccu_pll *p = hw_to_ccu_pll(hw); + struct ccu_common *common = &p->common; + u32 tmp; + + ccu_read(swcr1, common, &tmp); + reg5 = (tmp & PLL_SWCR1_REG5_MASK) >> PLL_SWCR1_REG5_OFF; + reg6 = (tmp & PLL_SWCR1_REG6_MASK) >> PLL_SWCR1_REG6_OFF; + reg7 = (tmp & PLL_SWCR1_REG7_MASK) >> PLL_SWCR1_REG7_OFF; + reg8 = (tmp & PLL_SWCR1_REG8_MASK) >> PLL_SWCR1_REG8_OFF; + + ccu_read(swcr3, common, &tmp); + div_int = (tmp & PLL_SWCR3_DIV_INT_MASK) >> PLL_SWCR3_DIV_INT_OFF; + div_frc = (tmp & PLL_SWCR3_DIV_FRC_MASK) >> PLL_SWCR3_DIV_FRC_OFF; + + freq_pll_regs_table = p->pll.rate_tbl; + size = p->pll.tbl_size; + + for (i = 0; i < size; i++) + if ((freq_pll_regs_table[i].reg5 == reg5) && + (freq_pll_regs_table[i].reg6 == reg6) && + (freq_pll_regs_table[i].reg7 == reg7) && + (freq_pll_regs_table[i].reg8 == reg8) && + (freq_pll_regs_table[i].div_int == div_int) && + (freq_pll_regs_table[i].div_frac == div_frc)) + return freq_pll_regs_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_base, + 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); +} + +/* + * pll rate change requires sequence: + * clock off -> change rate setting -> clock on + * This function doesn't really change rate, but cache the config + */ +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; + struct ccu_pll_rate_tbl *entry = NULL; + u32 mask, val; + int i; + + for (i = 0; i < params->tbl_size; i++) { + if (rate == params->rate_tbl[i].rate) { + entry = ¶ms->rate_tbl[i]; + break; + } + } + + if (WARN_ON_ONCE(!entry)) + return -EINVAL; + + mask = PLL_SWCR1_REG5_MASK | PLL_SWCR1_REG6_MASK; + mask |= PLL_SWCR1_REG7_MASK | PLL_SWCR1_REG8_MASK; + val = entry->reg5 << PLL_SWCR1_REG5_OFF; + val |= entry->reg6 << PLL_SWCR1_REG6_OFF; + val |= entry->reg7 << PLL_SWCR1_REG7_OFF; + val |= entry->reg8 << PLL_SWCR1_REG8_OFF; + ccu_update(swcr1, common, mask, val); + + mask = PLL_SWCR3_DIV_INT_MASK | PLL_SWCR3_DIV_FRC_MASK; + val = entry->div_int << PLL_SWCR3_DIV_INT_OFF; + val |= entry->div_frac << PLL_SWCR3_DIV_FRC_OFF; + ccu_update(swcr3, common, mask, val); + + 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 long max_rate = 0; + unsigned int i; + + for (i = 0; i < params->tbl_size; i++) { + if (params->rate_tbl[i].rate <= rate) { + if (max_rate < params->rate_tbl[i].rate) + max_rate = params->rate_tbl[i].rate; + } + } + + return MAX(max_rate, PLL_MIN_FREQ); +} + +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..d3397120414e --- /dev/null +++ b/drivers/clk/spacemit/ccu_pll.h @@ -0,0 +1,80 @@ +/* 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 long rate; + u32 reg5; + u32 reg6; + u32 reg7; + u32 reg8; + unsigned int div_int; + unsigned int div_frac; +}; + +struct ccu_pll_config { + struct ccu_pll_rate_tbl *rate_tbl; + u32 tbl_size; + u32 reg_lock; + u32 lock_enable_bit; +}; + +#define CCU_PLL_RATE(_rate, _reg5, _reg6, _reg7, _reg8, _div_int, _div_frac) \ + { \ + .rate = (_rate), \ + .reg5 = (_reg5), \ + .reg6 = (_reg6), \ + .reg7 = (_reg7), \ + .reg8 = (_reg8), \ + .div_int = (_div_int), \ + .div_frac = (_div_frac), \ + } + +struct ccu_pll { + struct ccu_pll_config pll; + struct ccu_common common; +}; + +#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \ + { \ + .rate_tbl = (struct ccu_pll_rate_tbl *)&(_table), \ + .tbl_size = ARRAY_SIZE(_table), \ + .reg_lock = (_reg_lock), \ + .lock_enable_bit = (_lock_enable_bit), \ + } + +#define CCU_PLL_HWINIT(_name, _flags) \ + CLK_HW_INIT_NO_PARENT(_name, &spacemit_ccu_pll_ops, _flags) + +#define CCU_PLL_DEFINE(_struct, _name, _table, _reg_swcr1, _reg_swcr2, \ + _reg_swcr3, _reg_lock, _lock_enable_bit, _flags) \ + \ + struct ccu_pll _struct = { \ + .pll = CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit), \ + .common = { \ + .reg_swcr1 = _reg_swcr1, \ + .reg_swcr2 = _reg_swcr2, \ + .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.47.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-01-03 21:56 ` [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu @ 2025-01-16 5:25 ` Samuel Holland 2025-01-21 17:01 ` Haylen Chu 2025-02-14 4:04 ` Alex Elder 1 sibling, 1 reply; 31+ messages in thread From: Samuel Holland @ 2025-01-16 5:25 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang Hi Haylen, On 2025-01-03 3:56 PM, Haylen Chu wrote: > The clock tree of K1 SoC contains three main types of clock hardware > (PLL/DDN/MIX) and is managed by several independent controllers in > different SoC parts (APBC, APBS and etc.), thus different compatible > strings are added to distinguish them. I was surprised that your DT binding didn't describe any clocks produced by one of these blocks and consumed by another one of them, so I took a look at the driver. > Some controllers may share IO region with reset controller and other low > speed peripherals like watchdog, so all register operations are done > through regmap to avoid competition. > > 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 | 1747 +++++++++++++++++++++++++++++ > drivers/clk/spacemit/ccu_common.h | 51 + > drivers/clk/spacemit/ccu_ddn.c | 140 +++ > drivers/clk/spacemit/ccu_ddn.h | 84 ++ > drivers/clk/spacemit/ccu_mix.c | 304 +++++ > drivers/clk/spacemit/ccu_mix.h | 309 +++++ > drivers/clk/spacemit/ccu_pll.c | 189 ++++ > drivers/clk/spacemit/ccu_pll.h | 80 ++ > 12 files changed, 2931 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 > > ... > > +/* 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, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > +}; > + > +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > +}; > + > +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > +}; > + > +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, > + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, > + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); > +static CCU_PLL_DEFINE(pll2, "pll2", pll2_rate_tbl, > + APB_SPARE7_REG, APB_SPARE8_REG, APB_SPARE9_REG, > + MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE); > +static CCU_PLL_DEFINE(pll3, "pll3", pll3_rate_tbl, > + APB_SPARE10_REG, APB_SPARE11_REG, APB_SPARE12_REG, > + MPMU_POSR, POSR_PLL3_LOCK, 0); > + > +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(1), BIT(1), 0, 2, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(2), BIT(2), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(3), BIT(3), 0, 4, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(4), BIT(4), 0, 5, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(5), BIT(5), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(6), BIT(6), 0, 7, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(7), BIT(7), 0, 8, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d11_223p4, "pll1_d11_223p4", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(15), BIT(15), 0, 11, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d13_189, "pll1_d13_189", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(16), BIT(16), 0, 13, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d23_106p8, "pll1_d23_106p8", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(20), BIT(20), 0, 23, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d64_38p4, "pll1_d64_38p4", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(0), BIT(0), 0, 64, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_245p7, "pll1_aud_245p7", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(10), BIT(10), 0, 10, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_24p5, "pll1_aud_24p5", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(11), BIT(11), 0, 100, 1, 0); > + > +static CCU_GATE_FACTOR_DEFINE(pll2_d1, "pll2_d1", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(0), BIT(0), 0, 1, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d2, "pll2_d2", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(1), BIT(1), 0, 2, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d3, "pll2_d3", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(2), BIT(2), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d4, "pll2_d4", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(3), BIT(3), 0, 4, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d5, "pll2_d5", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(4), BIT(4), 0, 5, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d6, "pll2_d6", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(5), BIT(5), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d7, "pll2_d7", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(6), BIT(6), 0, 7, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d8, "pll2_d8", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(7), BIT(7), 0, 8, 1, 0); > + > +static CCU_GATE_FACTOR_DEFINE(pll3_d1, "pll3_d1", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(0), BIT(0), 0, 1, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d2, "pll3_d2", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(1), BIT(1), 0, 2, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d3, "pll3_d3", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(2), BIT(2), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d4, "pll3_d4", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(3), BIT(3), 0, 4, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d5, "pll3_d5", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(4), BIT(4), 0, 5, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d6, "pll3_d6", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(5), BIT(5), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d7, "pll3_d7", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(6), BIT(6), 0, 7, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d8, "pll3_d8", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(7), BIT(7), 0, 8, 1, 0); > + > +static CCU_FACTOR_DEFINE(pll3_20, "pll3_20", CCU_PARENT_HW(pll3_d8), 20, 1); > +static CCU_FACTOR_DEFINE(pll3_40, "pll3_40", CCU_PARENT_HW(pll3_d8), 10, 1); > +static CCU_FACTOR_DEFINE(pll3_80, "pll3_80", CCU_PARENT_HW(pll3_d8), 5, 1); > + > +/* APBS clocks end */ > + > +/* MPMU clocks start */ > +static CCU_GATE_DEFINE(pll1_d8_307p2, "pll1_d8_307p2", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(13), BIT(13), 0, 0); Here you have a clk_hw from one driver instance (pll1_d8_307p2) pointing to a clk_hw from another driver instance (pll1_d8). If the consumer instance is probed first, pll1_d8 won't have been registered with the clock core, so you are quite likely to dereference a null hw->core pointer. The normal way to handle relationships between clock controller instances is by referencing the clocks in the consumer's devicetree node, and using .fw_name in the parent_data. Unfortunately, it seems there are quite a lot of clocks that would need to be included in the DT binding to do that. Regards, Samuel > ... > +/* APMU clocks end */ > + > +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { > + .hws = { > + [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, referring to here ^ > + [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, > + > + }, > + .num = CLK_APBS_NUM, > +}; > + > +static struct clk_hw_onecell_data k1_ccu_mpmu_clks = { > + .hws = { > + [CLK_PLL1_307P2] = &pll1_d8_307p2.common.hw, from here ^ > + [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_351] = &pll1_d7_351p08.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, > + }, > + .num = CLK_MPMU_NUM, > +}; > + > +static struct clk_hw_onecell_data k1_ccu_apbc_clks = { > + .hws = { > + [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, > + }, > + .num = CLK_APBC_NUM, > +}; > + > +static struct clk_hw_onecell_data k1_ccu_apmu_clks = { > + .hws = { > + [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] = &pcie0_clk.common.hw, > + [CLK_PCIE1] = &pcie1_clk.common.hw, > + [CLK_PCIE2] = &pcie2_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, > + }, > + .num = CLK_APMU_NUM > +}; > + > +struct spacemit_ccu_data { > + struct clk_hw_onecell_data *hw_clks; > + bool need_pll_lock; > +}; > + > +struct spacemit_ccu_priv { > + const struct spacemit_ccu_data *data; > + struct regmap *base; > + struct regmap *lock_base; > +}; > + > +static int spacemit_ccu_register(struct device *dev, > + struct spacemit_ccu_priv *priv) > +{ > + const struct spacemit_ccu_data *data = priv->data; > + int i, ret; > + > + for (i = 0; i < data->hw_clks->num; i++) { > + struct clk_hw *hw = data->hw_clks->hws[i]; > + struct ccu_common *common; > + const char *name; > + > + if (!hw) > + continue; > + > + common = hw_to_ccu_common(hw); > + name = hw->init->name; > + > + common->base = priv->base; > + common->lock_base = priv->lock_base; > + > + ret = devm_clk_hw_register(dev, hw); > + if (ret) { > + dev_err(dev, "Cannot register clock %d - %s\n", > + i, name); > + return ret; > + } > + } > + > + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, > + data->hw_clks); > +} > + > +static int k1_ccu_probe(struct platform_device *pdev) > +{ > + const struct spacemit_ccu_data *data; > + struct regmap *base_map, *lock_map = NULL; > + struct device *dev = &pdev->dev; > + struct spacemit_ccu_priv *priv; > + struct device_node *parent; > + int ret; > + > + data = of_device_get_match_data(dev); > + if (WARN_ON(!data)) > + return -EINVAL; > + > + parent = of_get_parent(dev->of_node); > + base_map = syscon_node_to_regmap(parent); > + of_node_put(parent); > + > + if (IS_ERR(base_map)) > + return dev_err_probe(dev, PTR_ERR(base_map), > + "failed to get regmap\n"); > + > + if (data->need_pll_lock) { > + lock_map = syscon_regmap_lookup_by_phandle(dev->of_node, > + "spacemit,mpmu"); > + if (IS_ERR(lock_map)) > + return dev_err_probe(dev, PTR_ERR(lock_map), > + "failed to get lock regmap\n"); > + } > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->data = data; > + priv->base = base_map; > + priv->lock_base = lock_map; > + > + ret = spacemit_ccu_register(dev, priv); > + if (ret) > + return dev_err_probe(dev, ret, "failed to register clocks\n"); > + > + return 0; > +} > + > +static const struct spacemit_ccu_data k1_ccu_apbs_data = { > + .need_pll_lock = true, > + .hw_clks = &k1_ccu_apbs_clks, > +}; > + > +static const struct spacemit_ccu_data k1_ccu_mpmu_data = { > + .need_pll_lock = false, > + .hw_clks = &k1_ccu_mpmu_clks, > +}; > + > +static const struct spacemit_ccu_data k1_ccu_apbc_data = { > + .need_pll_lock = false, > + .hw_clks = &k1_ccu_apbc_clks, > +}; > + > +static const struct spacemit_ccu_data k1_ccu_apmu_data = { > + .need_pll_lock = false, > + .hw_clks = &k1_ccu_apmu_clks, > +}; > + > +static const struct of_device_id of_k1_ccu_match[] = { > + { > + .compatible = "spacemit,k1-ccu-apbs", > + .data = &k1_ccu_apbs_data, > + }, > + { > + .compatible = "spacemit,k1-ccu-mpmu", > + .data = &k1_ccu_mpmu_data, > + }, > + { > + .compatible = "spacemit,k1-ccu-apbc", > + .data = &k1_ccu_apbc_data, > + }, > + { > + .compatible = "spacemit,k1-ccu-apmu", > + .data = &k1_ccu_apmu_data, > + }, > + { } > +}; > +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 > ... ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-01-16 5:25 ` Samuel Holland @ 2025-01-21 17:01 ` Haylen Chu 0 siblings, 0 replies; 31+ messages in thread From: Haylen Chu @ 2025-01-21 17:01 UTC (permalink / raw) To: Samuel Holland, Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Haylen Chu, Yixun Lan Cc: linux-riscv, linux-clk, devicetree, linux-kernel, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang On Wed, Jan 15, 2025 at 11:25:15PM -0600, Samuel Holland wrote: > Hi Haylen, > > On 2025-01-03 3:56 PM, Haylen Chu wrote: > > The clock tree of K1 SoC contains three main types of clock hardware > > (PLL/DDN/MIX) and is managed by several independent controllers in > > different SoC parts (APBC, APBS and etc.), thus different compatible > > strings are added to distinguish them. > > I was surprised that your DT binding didn't describe any clocks produced by one > of these blocks and consumed by another one of them, so I took a look at the driver. > > > Some controllers may share IO region with reset controller and other low > > speed peripherals like watchdog, so all register operations are done > > through regmap to avoid competition. > > > > 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 | 1747 +++++++++++++++++++++++++++++ > > drivers/clk/spacemit/ccu_common.h | 51 + > > drivers/clk/spacemit/ccu_ddn.c | 140 +++ > > drivers/clk/spacemit/ccu_ddn.h | 84 ++ > > drivers/clk/spacemit/ccu_mix.c | 304 +++++ > > drivers/clk/spacemit/ccu_mix.h | 309 +++++ > > drivers/clk/spacemit/ccu_pll.c | 189 ++++ > > drivers/clk/spacemit/ccu_pll.h | 80 ++ > > 12 files changed, 2931 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 > > > > ... > > > > +/* 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, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > > +}; > > + > > +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { > > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > > +}; > > + > > +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { > > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > > + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), > > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > > +}; > > + > > +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, > > + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, > > + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); > > +static CCU_PLL_DEFINE(pll2, "pll2", pll2_rate_tbl, > > + APB_SPARE7_REG, APB_SPARE8_REG, APB_SPARE9_REG, > > + MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE); > > +static CCU_PLL_DEFINE(pll3, "pll3", pll3_rate_tbl, > > + APB_SPARE10_REG, APB_SPARE11_REG, APB_SPARE12_REG, > > + MPMU_POSR, POSR_PLL3_LOCK, 0); > > + > > +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d11_223p4, "pll1_d11_223p4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(15), BIT(15), 0, 11, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d13_189, "pll1_d13_189", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(16), BIT(16), 0, 13, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d23_106p8, "pll1_d23_106p8", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(20), BIT(20), 0, 23, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d64_38p4, "pll1_d64_38p4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(0), BIT(0), 0, 64, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_245p7, "pll1_aud_245p7", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(10), BIT(10), 0, 10, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_24p5, "pll1_aud_24p5", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(11), BIT(11), 0, 100, 1, 0); > > + > > +static CCU_GATE_FACTOR_DEFINE(pll2_d1, "pll2_d1", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(0), BIT(0), 0, 1, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d2, "pll2_d2", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d3, "pll2_d3", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d4, "pll2_d4", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d5, "pll2_d5", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d6, "pll2_d6", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d7, "pll2_d7", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d8, "pll2_d8", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > + > > +static CCU_GATE_FACTOR_DEFINE(pll3_d1, "pll3_d1", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(0), BIT(0), 0, 1, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d2, "pll3_d2", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d3, "pll3_d3", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d4, "pll3_d4", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d5, "pll3_d5", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d6, "pll3_d6", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d7, "pll3_d7", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d8, "pll3_d8", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > + > > +static CCU_FACTOR_DEFINE(pll3_20, "pll3_20", CCU_PARENT_HW(pll3_d8), 20, 1); > > +static CCU_FACTOR_DEFINE(pll3_40, "pll3_40", CCU_PARENT_HW(pll3_d8), 10, 1); > > +static CCU_FACTOR_DEFINE(pll3_80, "pll3_80", CCU_PARENT_HW(pll3_d8), 5, 1); > > + > > +/* APBS clocks end */ > > + > > +/* MPMU clocks start */ > > +static CCU_GATE_DEFINE(pll1_d8_307p2, "pll1_d8_307p2", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(13), BIT(13), 0, 0); > > Here you have a clk_hw from one driver instance (pll1_d8_307p2) pointing to a > clk_hw from another driver instance (pll1_d8). Yes, there're cross references between different controllers, indexed with struct clk_hw. I have to admit that it isn't a common design pattern among clock drivers. > If the consumer instance is > probed first, pll1_d8 won't have been registered with the clock core, so you are > quite likely to dereference a null hw->core pointer. But I don't think null pointer dereferencing would happen. When a struct clk_hw gets registered through devm_clk_hw_register(), the real parenting work is done in __clk_init_parent() called by __clk_core_init(): the former'll find struct clk_core of the parent isn't initialized, thus invoke clk_core_fill_parent_index() to try a search. In this case, the search will always fail since the parent hasn't been registered as struct clk_core. So __clk_core_init() will mark it as an orphan and afaik CCF won't touch the core field of its parent at all. As far as we keep in mind that stuff like clk_hw_get_parent_by_index() may fail when writing the driver, everything should work. > The normal way to handle relationships between clock controller instances is by > referencing the clocks in the consumer's devicetree node, and using .fw_name in > the parent_data. Unfortunately, it seems there are quite a lot of clocks that > would need to be included in the DT binding to do that. Yeah. As Krzysztof has concerns on splitting multiple clock controllers as well, it's necessary to discuss possibility of merging them together (then we're more likely to make the driver bug-free). Thanks for your time and review! > Regards, > Samuel > Best regards, Haylen Chu > > ... > > +/* APMU clocks end */ > > + > > +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { > > + .hws = { > > + [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, > > referring to here ^ > > > + [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, > > + > > + }, > > + .num = CLK_APBS_NUM, > > +}; > > + > > +static struct clk_hw_onecell_data k1_ccu_mpmu_clks = { > > + .hws = { > > + [CLK_PLL1_307P2] = &pll1_d8_307p2.common.hw, > > from here ^ > > > + [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_351] = &pll1_d7_351p08.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, > > + }, > > + .num = CLK_MPMU_NUM, > > +}; > > + > > +static struct clk_hw_onecell_data k1_ccu_apbc_clks = { > > + .hws = { > > + [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, > > + }, > > + .num = CLK_APBC_NUM, > > +}; > > + > > +static struct clk_hw_onecell_data k1_ccu_apmu_clks = { > > + .hws = { > > + [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] = &pcie0_clk.common.hw, > > + [CLK_PCIE1] = &pcie1_clk.common.hw, > > + [CLK_PCIE2] = &pcie2_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, > > + }, > > + .num = CLK_APMU_NUM > > +}; > > + > > +struct spacemit_ccu_data { > > + struct clk_hw_onecell_data *hw_clks; > > + bool need_pll_lock; > > +}; > > + > > +struct spacemit_ccu_priv { > > + const struct spacemit_ccu_data *data; > > + struct regmap *base; > > + struct regmap *lock_base; > > +}; > > + > > +static int spacemit_ccu_register(struct device *dev, > > + struct spacemit_ccu_priv *priv) > > +{ > > + const struct spacemit_ccu_data *data = priv->data; > > + int i, ret; > > + > > + for (i = 0; i < data->hw_clks->num; i++) { > > + struct clk_hw *hw = data->hw_clks->hws[i]; > > + struct ccu_common *common; > > + const char *name; > > + > > + if (!hw) > > + continue; > > + > > + common = hw_to_ccu_common(hw); > > + name = hw->init->name; > > + > > + common->base = priv->base; > > + common->lock_base = priv->lock_base; > > + > > + ret = devm_clk_hw_register(dev, hw); > > + if (ret) { > > + dev_err(dev, "Cannot register clock %d - %s\n", > > + i, name); > > + return ret; > > + } > > + } > > + > > + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, > > + data->hw_clks); > > +} > > + > > +static int k1_ccu_probe(struct platform_device *pdev) > > +{ > > + const struct spacemit_ccu_data *data; > > + struct regmap *base_map, *lock_map = NULL; > > + struct device *dev = &pdev->dev; > > + struct spacemit_ccu_priv *priv; > > + struct device_node *parent; > > + int ret; > > + > > + data = of_device_get_match_data(dev); > > + if (WARN_ON(!data)) > > + return -EINVAL; > > + > > + parent = of_get_parent(dev->of_node); > > + base_map = syscon_node_to_regmap(parent); > > + of_node_put(parent); > > + > > + if (IS_ERR(base_map)) > > + return dev_err_probe(dev, PTR_ERR(base_map), > > + "failed to get regmap\n"); > > + > > + if (data->need_pll_lock) { > > + lock_map = syscon_regmap_lookup_by_phandle(dev->of_node, > > + "spacemit,mpmu"); > > + if (IS_ERR(lock_map)) > > + return dev_err_probe(dev, PTR_ERR(lock_map), > > + "failed to get lock regmap\n"); > > + } > > + > > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > > + if (!priv) > > + return -ENOMEM; > > + > > + priv->data = data; > > + priv->base = base_map; > > + priv->lock_base = lock_map; > > + > > + ret = spacemit_ccu_register(dev, priv); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to register clocks\n"); > > + > > + return 0; > > +} > > + > > +static const struct spacemit_ccu_data k1_ccu_apbs_data = { > > + .need_pll_lock = true, > > + .hw_clks = &k1_ccu_apbs_clks, > > +}; > > + > > +static const struct spacemit_ccu_data k1_ccu_mpmu_data = { > > + .need_pll_lock = false, > > + .hw_clks = &k1_ccu_mpmu_clks, > > +}; > > + > > +static const struct spacemit_ccu_data k1_ccu_apbc_data = { > > + .need_pll_lock = false, > > + .hw_clks = &k1_ccu_apbc_clks, > > +}; > > + > > +static const struct spacemit_ccu_data k1_ccu_apmu_data = { > > + .need_pll_lock = false, > > + .hw_clks = &k1_ccu_apmu_clks, > > +}; > > + > > +static const struct of_device_id of_k1_ccu_match[] = { > > + { > > + .compatible = "spacemit,k1-ccu-apbs", > > + .data = &k1_ccu_apbs_data, > > + }, > > + { > > + .compatible = "spacemit,k1-ccu-mpmu", > > + .data = &k1_ccu_mpmu_data, > > + }, > > + { > > + .compatible = "spacemit,k1-ccu-apbc", > > + .data = &k1_ccu_apbc_data, > > + }, > > + { > > + .compatible = "spacemit,k1-ccu-apmu", > > + .data = &k1_ccu_apmu_data, > > + }, > > + { } > > +}; > > +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 > > ... ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-01-03 21:56 ` [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu 2025-01-16 5:25 ` Samuel Holland @ 2025-02-14 4:04 ` Alex Elder 2025-02-16 11:34 ` Haylen Chu 2025-03-03 9:41 ` Haylen Chu 1 sibling, 2 replies; 31+ messages in thread From: Alex Elder @ 2025-02-14 4:04 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, Inochi Amaoto, Chen Wang, Guodong Xu On 1/3/25 3:56 PM, Haylen Chu wrote: > The clock tree of K1 SoC contains three main types of clock hardware > (PLL/DDN/MIX) and is managed by several independent controllers in > different SoC parts (APBC, APBS and etc.), thus different compatible > strings are added to distinguish them. > > Some controllers may share IO region with reset controller and other low > speed peripherals like watchdog, so all register operations are done > through regmap to avoid competition. > > Signed-off-by: Haylen Chu <heylenay@4d2.org> This is a really big patch (over 3000 lines), and a fairly large amount of code to review. But I've given it a really thorough read and I have a *lot* of review comments for you to consider. First, a few top-level comments. - This driver is very comprehensive. It represents essentially *all* of the clocks in the tree diagram shown here: https://developer.spacemit.com/resource/file/images?fileName=DkWGb4ed7oAziVxE6PIcbjTLnpd.png (I can tell you what's missing but I don't think it matters.) - In almost all cases, the names of the clocks match the names shown in that diagram, which is very helpful. - All of the clocks are implemented using "custom" clock implementations. I'm fairly certain that almost all of them can use standard clock framework types instead (fixed-rate, fixed-factor, fractional-divider, mux, and composite). But for now I think there are other things more important to improve. - A great deal of my commentary below is simply saying that the code is more complex than necessary. Some simple (though widespread) refactoring would improve things a lot. And some of the definitions can be done without having to specify nearly so many values. - Much of what might be considered generality in the implementation actually isn't needed, because it isn't used. This is especially true given that there are essentially no clocks left unspecified for the K1 SoC. - Once the refactoring I suggest has been done, I expect that more opportunities for simplification and cleanup will become obvious; we'll see. - I suggest these changes because the resulting simplicity will make the code much more understandable and maintainable in the long term. And if it's simpler to understand, it should be easier for a maintainer to accept. I'm not going to comment on the things related to Device Tree that have already been mentioned, nor on the Makefile or Kconfig, etc. I'm focusing just on the code. > --- > drivers/clk/Kconfig | 1 + > drivers/clk/Makefile | 1 + > drivers/clk/spacemit/Kconfig | 20 + > drivers/clk/spacemit/Makefile | 5 + > drivers/clk/spacemit/ccu-k1.c | 1747 +++++++++++++++++++++++++++++ > drivers/clk/spacemit/ccu_common.h | 51 + > drivers/clk/spacemit/ccu_ddn.c | 140 +++ > drivers/clk/spacemit/ccu_ddn.h | 84 ++ > drivers/clk/spacemit/ccu_mix.c | 304 +++++ > drivers/clk/spacemit/ccu_mix.h | 309 +++++ > drivers/clk/spacemit/ccu_pll.c | 189 ++++ > drivers/clk/spacemit/ccu_pll.h | 80 ++ > 12 files changed, 2931 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 299bc678ed1b..fa8b5e8f2926 100644 > --- a/drivers/clk/Kconfig > +++ b/drivers/clk/Kconfig > @@ -498,6 +498,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 fb8878a5d7d9..e82ece2d0095 100644 > --- a/drivers/clk/Makefile > +++ b/drivers/clk/Makefile > @@ -129,6 +129,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..6fb0a12ec261 > --- /dev/null > +++ b/drivers/clk/spacemit/ccu-k1.c > @@ -0,0 +1,1747 @@ > +// 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/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 */ What do APBS, MPMU, APBC, and APMU stand for and represent? It seems each has its own clock controller, but can you offer any explanation about why they're divided that way? > +/* pll1 */ These first few registers are found in the "APB_SPARE" space. But what they represent are PLL control registers. I'd rather have these take on their more meaningful names, like: PLL1_SW1_CTRL PLL1_SW3_CTRL PLL2_SW1_CTRL, etc. > +#define APB_SPARE1_REG 0x100 > +#define APB_SPARE2_REG 0x104 The above register, as well as APB_SPARE8_REG and APB_SPARE11_REG below, are not actually used. More on this later. > +#define APB_SPARE3_REG 0x108 > +/* pll2 */ > +#define APB_SPARE7_REG 0x118 > +#define APB_SPARE8_REG 0x11c > +#define APB_SPARE9_REG 0x120 > +/* pll3 */ > +#define APB_SPARE10_REG 0x124 > +#define APB_SPARE11_REG 0x128 > +#define APB_SPARE12_REG 0x12c > + > +/* MPMU register offset */ > +#define MPMU_POSR 0x10 This register is used to signal when enabling a PLL has completed, with the 3 bits defined next corresponding to the three PLLs. This is OK, but it seems weird that this register is in the MPMU space, while the registers controlling the clocks themselves are in APBC space. It makes the probe a little more complicated, because it requires the MPMU controller probe to complete before the APBC probe can. I guess that's not really a question or suggestion, it's an oddity I wanted to point out. If you have any more information about this, please share it. > +#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 The APBC_COUNTER_CLK_SEL register offset is never used. > +#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_CCI550_CLK_CTRL 0x300 > +#define APMU_CPU_C0_CLK_CTRL 0x38C > +#define APMU_CPU_C1_CLK_CTRL 0x390 > +#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 APMU_USB_CLK_RES_CTRL is duplicated below. > +#define APMU_USB_CLK_RES_CTRL 0x5c > +#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 APMU_PMUA_MC_CTRL is never used, nor is APMU_PMU_CC2_AP. > +#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 APMU_CCI550_CLK_CTRL is duplicated below, as are APMU_ACLK_CLK_CTRL, APMU_CPU_C0_CLK_CTRL, and APMU_CPU_C1_CLK_CTRL. > +#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 */ I'm going to say more about this below, but these definitions can be simplified a lot. For example, the first one could be: CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd), > +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = { > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > +}; > + > +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > +}; > + > +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > +}; > + Therre are only three PLL clocks, defined next. All share the same lock register offset (MPMU_POSR), so that could be factored out. > +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, > + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, > + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); > +static CCU_PLL_DEFINE(pll2, "pll2", pll2_rate_tbl, > + APB_SPARE7_REG, APB_SPARE8_REG, APB_SPARE9_REG, > + MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE); > +static CCU_PLL_DEFINE(pll3, "pll3", pll3_rate_tbl, > + APB_SPARE10_REG, APB_SPARE11_REG, APB_SPARE12_REG, > + MPMU_POSR, POSR_PLL3_LOCK, 0); > + The next set of clocks differ from essentially all others, in that they don't encode their frequency in the name. I.e., I would expect the first one to be named pll1_d2_1228p8. > +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(1), BIT(1), 0, 2, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(2), BIT(2), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(3), BIT(3), 0, 4, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(4), BIT(4), 0, 5, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(5), BIT(5), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(6), BIT(6), 0, 7, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(7), BIT(7), 0, 8, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d11_223p4, "pll1_d11_223p4", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(15), BIT(15), 0, 11, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d13_189, "pll1_d13_189", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(16), BIT(16), 0, 13, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d23_106p8, "pll1_d23_106p8", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(20), BIT(20), 0, 23, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d64_38p4, "pll1_d64_38p4", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(0), BIT(0), 0, 64, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_245p7, "pll1_aud_245p7", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(10), BIT(10), 0, 10, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_24p5, "pll1_aud_24p5", CCU_PARENT_HW(pll1), > + APB_SPARE2_REG, > + BIT(11), BIT(11), 0, 100, 1, 0); > + > +static CCU_GATE_FACTOR_DEFINE(pll2_d1, "pll2_d1", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(0), BIT(0), 0, 1, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d2, "pll2_d2", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(1), BIT(1), 0, 2, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d3, "pll2_d3", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(2), BIT(2), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d4, "pll2_d4", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(3), BIT(3), 0, 4, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d5, "pll2_d5", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(4), BIT(4), 0, 5, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d6, "pll2_d6", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(5), BIT(5), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d7, "pll2_d7", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(6), BIT(6), 0, 7, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll2_d8, "pll2_d8", CCU_PARENT_HW(pll2), > + APB_SPARE8_REG, > + BIT(7), BIT(7), 0, 8, 1, 0); > + > +static CCU_GATE_FACTOR_DEFINE(pll3_d1, "pll3_d1", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(0), BIT(0), 0, 1, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d2, "pll3_d2", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(1), BIT(1), 0, 2, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d3, "pll3_d3", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(2), BIT(2), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d4, "pll3_d4", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(3), BIT(3), 0, 4, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d5, "pll3_d5", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(4), BIT(4), 0, 5, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d6, "pll3_d6", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(5), BIT(5), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d7, "pll3_d7", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(6), BIT(6), 0, 7, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll3_d8, "pll3_d8", CCU_PARENT_HW(pll3), > + APB_SPARE11_REG, > + BIT(7), BIT(7), 0, 8, 1, 0); > + > +static CCU_FACTOR_DEFINE(pll3_20, "pll3_20", CCU_PARENT_HW(pll3_d8), 20, 1); > +static CCU_FACTOR_DEFINE(pll3_40, "pll3_40", CCU_PARENT_HW(pll3_d8), 10, 1); > +static CCU_FACTOR_DEFINE(pll3_80, "pll3_80", CCU_PARENT_HW(pll3_d8), 5, 1); > + > +/* APBS clocks end */ > + > +/* MPMU clocks start */ > +static CCU_GATE_DEFINE(pll1_d8_307p2, "pll1_d8_307p2", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(13), BIT(13), 0, 0); > +static CCU_FACTOR_DEFINE(pll1_d32_76p8, "pll1_d32_76p8", CCU_PARENT_HW(pll1_d8_307p2), > + 4, 1); > +static CCU_FACTOR_DEFINE(pll1_d40_61p44, "pll1_d40_61p44", CCU_PARENT_HW(pll1_d8_307p2), > + 5, 1); > +static CCU_FACTOR_DEFINE(pll1_d16_153p6, "pll1_d16_153p6", CCU_PARENT_HW(pll1_d8), > + 2, 1); > +static CCU_GATE_FACTOR_DEFINE(pll1_d24_102p4, "pll1_d24_102p4", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(12), BIT(12), 0, 3, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2, "pll1_d48_51p2", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(7), BIT(7), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2_ap, "pll1_d48_51p2_ap", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(11), BIT(11), 0, 6, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_m3d128_57p6, "pll1_m3d128_57p6", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(8), BIT(8), 0, 16, 3, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d96_25p6, "pll1_d96_25p6", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(4), BIT(4), 0, 12, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8, "pll1_d192_12p8", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(3), BIT(3), 0, 24, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8_wdt, "pll1_d192_12p8_wdt", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(19), BIT(19), 0x0, 24, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d384_6p4, "pll1_d384_6p4", CCU_PARENT_HW(pll1_d8), > + MPMU_ACGR, > + BIT(2), BIT(2), 0, 48, 1, 0); > +static CCU_FACTOR_DEFINE(pll1_d768_3p2, "pll1_d768_3p2", CCU_PARENT_HW(pll1_d384_6p4), > + 2, 1); > +static CCU_FACTOR_DEFINE(pll1_d1536_1p6, "pll1_d1536_1p6", CCU_PARENT_HW(pll1_d384_6p4), > + 4, 1); > +static CCU_FACTOR_DEFINE(pll1_d3072_0p8, "pll1_d3072_0p8", CCU_PARENT_HW(pll1_d384_6p4), > + 8, 1); > + The next clock is weird, and it's the only one of its kind. It is not represented in the clock tree diagram. It is a "factor 1" clock (so it just passes the parent's rate through), and has no gate. Do you know why it's defined? It is used only as one of the MPMU parent clocks. Why isn't just the pll1_d7 clock used in its place? > +static CCU_FACTOR_DEFINE(pll1_d7_351p08, "pll1_d7_351p08", CCU_PARENT_HW(pll1_d7), > + 1, 1); > + > +static CCU_GATE_DEFINE(pll1_d6_409p6, "pll1_d6_409p6", CCU_PARENT_HW(pll1_d6), > + MPMU_ACGR, > + BIT(0), BIT(0), 0, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d12_204p8, "pll1_d12_204p8", CCU_PARENT_HW(pll1_d6), > + MPMU_ACGR, > + BIT(5), BIT(5), 0, 2, 1, 0); > + > +static CCU_GATE_DEFINE(pll1_d5_491p52, "pll1_d5_491p52", CCU_PARENT_HW(pll1_d5), > + MPMU_ACGR, > + BIT(21), BIT(21), 0, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d10_245p76, "pll1_d10_245p76", CCU_PARENT_HW(pll1_d5), > + MPMU_ACGR, > + BIT(18), BIT(18), 0, 2, 1, 0); > + > +static CCU_GATE_DEFINE(pll1_d4_614p4, "pll1_d4_614p4", CCU_PARENT_HW(pll1_d4), > + MPMU_ACGR, > + BIT(15), BIT(15), 0, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d52_47p26, "pll1_d52_47p26", CCU_PARENT_HW(pll1_d4), > + MPMU_ACGR, > + BIT(10), BIT(10), 0, 13, 1, 0); > +static CCU_GATE_FACTOR_DEFINE(pll1_d78_31p5, "pll1_d78_31p5", CCU_PARENT_HW(pll1_d4), > + MPMU_ACGR, > + BIT(6), BIT(6), 0, 39, 2, 0); > + > +static CCU_GATE_DEFINE(pll1_d3_819p2, "pll1_d3_819p2", CCU_PARENT_HW(pll1_d3), > + MPMU_ACGR, > + BIT(14), BIT(14), 0, 0); > + > +static CCU_GATE_DEFINE(pll1_d2_1228p8, "pll1_d2_1228p8", CCU_PARENT_HW(pll1_d2), > + MPMU_ACGR, > + BIT(16), BIT(16), 0, 0); > +static struct ccu_ddn_info uart_ddn_mask_info = { > + .factor = 2, > + .num_mask = 0x1fff, > + .den_mask = 0x1fff, > + .num_shift = 16, > + .den_shift = 0, > +}; I think ccu_ddn_tbl structure can go away. More below. > +static struct ccu_ddn_tbl slow_uart1_tbl[] = { > + { .num = 125, .den = 24 }, > +}; > +static struct ccu_ddn_tbl slow_uart2_tbl[] = { > + { .num = 6144, .den = 960 }, > +}; I'll note here that this "slow_uart" gate is defined separately from the two slow UART DDN clocks below. That's different from some other gated clocks. > +static CCU_GATE_DEFINE(slow_uart, "slow_uart", CCU_PARENT_NAME(osc), > + MPMU_ACGR, > + BIT(1), BIT(1), 0, CLK_IGNORE_UNUSED); What follows are the only two DDN clocks defined. Both define a "table" of numerator/denominator pairs, but in both cases, the table has only one entry. Given that, why not simply encode the numerator and denominator in the ccu_info structure for each of these DDN clock instances? > +static CCU_DDN_DEFINE(slow_uart1_14p74, "slow_uart1_14p74", pll1_d16_153p6, > + &uart_ddn_mask_info, slow_uart1_tbl, > + MPMU_SUCCR, 0); > +static CCU_DDN_DEFINE(slow_uart2_48, "slow_uart2_48", pll1_d4_614p4, > + &uart_ddn_mask_info, slow_uart2_tbl, > + MPMU_SUCCR_1, 0); > + > +static CCU_GATE_DEFINE(wdt_clk, "wdt_clk", CCU_PARENT_HW(pll1_d96_25p6), > + MPMU_WDTPCR, > + BIT(1), BIT(1), 0x0, > + 0); > + I couldn't find the "ripc_clk" on the clock tree diagram. It is never used elsewhere, so I think this definition can go away. > +static CCU_GATE_DEFINE(ripc_clk, "ripc_clk", CCU_PARENT_NAME(vctcxo_24m), > + MPMU_RIPCCR, > + 0x3, 0x3, 0x0, > + 0); > + > +static CCU_GATE_FACTOR_DEFINE(i2s_sysclk, "i2s_sysclk", CCU_PARENT_HW(pll1_d16_153p6), > + MPMU_ISCCR, > + BIT(31), BIT(31), 0x0, 50, 1, > + 0); > +static CCU_GATE_FACTOR_DEFINE(i2s_bclk, "i2s_bclk", CCU_PARENT_HW(i2s_sysclk), > + MPMU_ISCCR, > + BIT(29), BIT(29), 0x0, 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_clk", apb_parents, > + MPMU_APBCSCR, > + 0, 2, > + 0); > + The following clock is just called "wdt_clk" on the clock tree diagram. > +static CCU_GATE_DEFINE(wdt_bus_clk, "wdt_bus_clk", CCU_PARENT_HW(apb_clk), > + MPMU_WDTPCR, > + BIT(2), BIT(2), 0x0, > + 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), > +}; I might be misunderstanding this, but all of the 9 UART clocks below share a common gate. I *think* that gate should be represented as a distinct clock so that it can be properly reference-counted. There are numerous cases of this. Also note there is no uart1 clock; I think the clock tree diagram erroneously calls the first clock "uart1" (not uart0). > +static CCU_MUX_GATE_DEFINE(uart0_clk, "uart0_clk", uart_clk_parents, > + APBC_UART1_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + CLK_IS_CRITICAL); > +static CCU_MUX_GATE_DEFINE(uart2_clk, "uart2_clk", uart_clk_parents, > + APBC_UART2_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart3_clk, "uart3_clk", uart_clk_parents, > + APBC_UART3_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart4_clk, "uart4_clk", uart_clk_parents, > + APBC_UART4_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart5_clk, "uart5_clk", uart_clk_parents, > + APBC_UART5_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart6_clk, "uart6_clk", uart_clk_parents, > + APBC_UART6_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart7_clk, "uart7_clk", uart_clk_parents, > + APBC_UART7_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart8_clk, "uart8_clk", uart_clk_parents, > + APBC_UART8_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(uart9_clk, "uart9_clk", uart_clk_parents, > + APBC_UART9_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(gpio_clk, "gpio_clk", CCU_PARENT_NAME(vctcxo_24m), > + APBC_GPIO_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 0); > + > +static const struct clk_parent_data pwm_parents[] = { > + CCU_PARENT_HW(pll1_d192_12p8), > + CCU_PARENT_NAME(osc), > +}; The 20 PMIC clocks share a common gate, and this is another example where I think that should be represented separate from all of these. (I'm not going to point out any more such instances.) > +static CCU_MUX_GATE_DEFINE(pwm0_clk, "pwm0_clk", pwm_parents, > + APBC_PWM0_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm1_clk, "pwm1_clk", pwm_parents, > + APBC_PWM1_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm2_clk, "pwm2_clk", pwm_parents, > + APBC_PWM2_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm3_clk, "pwm3_clk", pwm_parents, > + APBC_PWM3_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm4_clk, "pwm4_clk", pwm_parents, > + APBC_PWM4_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm5_clk, "pwm5_clk", pwm_parents, > + APBC_PWM5_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm6_clk, "pwm6_clk", pwm_parents, > + APBC_PWM6_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm7_clk, "pwm7_clk", pwm_parents, > + APBC_PWM7_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm8_clk, "pwm8_clk", pwm_parents, > + APBC_PWM8_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm9_clk, "pwm9_clk", pwm_parents, > + APBC_PWM9_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm10_clk, "pwm10_clk", pwm_parents, > + APBC_PWM10_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm11_clk, "pwm11_clk", pwm_parents, > + APBC_PWM11_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm12_clk, "pwm12_clk", pwm_parents, > + APBC_PWM12_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm13_clk, "pwm13_clk", pwm_parents, > + APBC_PWM13_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm14_clk, "pwm14_clk", pwm_parents, > + APBC_PWM14_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm15_clk, "pwm15_clk", pwm_parents, > + APBC_PWM15_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm16_clk, "pwm16_clk", pwm_parents, > + APBC_PWM16_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm17_clk, "pwm17_clk", pwm_parents, > + APBC_PWM17_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm18_clk, "pwm18_clk", pwm_parents, > + APBC_PWM18_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(pwm19_clk, "pwm19_clk", pwm_parents, > + APBC_PWM19_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 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, "ssp3_clk", ssp_parents, > + APBC_SSP3_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(rtc_clk, "rtc_clk", CCU_PARENT_NAME(osc), > + APBC_RTC_CLK_RST, > + 0x82, 0x82, 0x0, > + 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, "twsi0_clk", twsi_parents, > + APBC_TWSI0_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi1_clk, "twsi1_clk", twsi_parents, > + APBC_TWSI1_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi2_clk, "twsi2_clk", twsi_parents, > + APBC_TWSI2_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi4_clk, "twsi4_clk", twsi_parents, > + APBC_TWSI4_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi5_clk, "twsi5_clk", twsi_parents, > + APBC_TWSI5_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi6_clk, "twsi6_clk", twsi_parents, > + APBC_TWSI6_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi7_clk, "twsi7_clk", twsi_parents, > + APBC_TWSI7_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(twsi8_clk, "twsi8_clk", twsi_parents, > + APBC_TWSI8_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 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, "timers1_clk", timer_parents, > + APBC_TIMERS1_CLK_RST, > + 4, 3, 0x3, 0x3, 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(timers2_clk, "timers2_clk", timer_parents, > + APBC_TIMERS2_CLK_RST, > + 4, 3, 0x3, 0x3, 0x0, > + 0); > + > +static CCU_GATE_DEFINE(aib_clk, "aib_clk", CCU_PARENT_NAME(vctcxo_24m), > + APBC_AIB_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(onewire_clk, "onewire_clk", CCU_PARENT_NAME(vctcxo_24m), > + APBC_ONEWIRE_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 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, "sspa0_clk", sspa_parents, > + APBC_SSPA0_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_MUX_GATE_DEFINE(sspa1_clk, "sspa1_clk", sspa_parents, > + APBC_SSPA1_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(dro_clk, "dro_clk", CCU_PARENT_HW(apb_clk), > + APBC_DRO_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(ir_clk, "ir_clk", CCU_PARENT_HW(apb_clk), > + APBC_IR_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(tsen_clk, "tsen_clk", CCU_PARENT_HW(apb_clk), > + APBC_TSEN_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(ipc_ap2aud_clk, "ipc_ap2aud_clk", CCU_PARENT_HW(apb_clk), > + APBC_IPC_AP2AUD_CLK_RST, > + BIT(1), BIT(1), 0x0, > + 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, "can0_clk", can_parents, > + APBC_CAN0_CLK_RST, > + 4, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(can0_bus_clk, "can0_bus_clk", CCU_PARENT_NAME(vctcxo_24m), > + APBC_CAN0_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); Here we begin a bunch of definitions of "bus clocks". They are all simply gates, and the clock tree diagram shows these paired with another gated parent. This says to me that in order to use a "child clock" in this situation, both the "regular gate" and the "bus gate" clock must be defined, and a reference to it taken (with clk_get() or similar). Can you confirm this? > + > +static CCU_GATE_DEFINE(uart0_bus_clk, "uart0_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART1_CLK_RST, > + BIT(0), BIT(0), 0x0, > + CLK_IS_CRITICAL); > +static CCU_GATE_DEFINE(uart2_bus_clk, "uart2_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART2_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart3_bus_clk, "uart3_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART3_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart4_bus_clk, "uart4_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART4_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart5_bus_clk, "uart5_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART5_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart6_bus_clk, "uart6_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART6_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart7_bus_clk, "uart7_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART7_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart8_bus_clk, "uart8_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART8_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(uart9_bus_clk, "uart9_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_UART9_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(gpio_bus_clk, "gpio_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_GPIO_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(pwm0_bus_clk, "pwm0_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM0_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm1_bus_clk, "pwm1_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM1_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm2_bus_clk, "pwm2_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM2_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm3_bus_clk, "pwm3_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM3_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm4_bus_clk, "pwm4_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM4_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm5_bus_clk, "pwm5_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM5_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm6_bus_clk, "pwm6_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM6_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm7_bus_clk, "pwm7_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM7_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm8_bus_clk, "pwm8_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM8_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm9_bus_clk, "pwm9_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM9_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm10_bus_clk, "pwm10_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM10_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm11_bus_clk, "pwm11_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM11_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm12_bus_clk, "pwm12_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM12_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm13_bus_clk, "pwm13_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM13_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm14_bus_clk, "pwm14_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM14_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm15_bus_clk, "pwm15_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM15_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm16_bus_clk, "pwm16_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM16_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm17_bus_clk, "pwm17_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM17_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm18_bus_clk, "pwm18_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM18_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(pwm19_bus_clk, "pwm19_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_PWM19_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(ssp3_bus_clk, "ssp3_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_SSP3_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(rtc_bus_clk, "rtc_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_RTC_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(twsi0_bus_clk, "twsi0_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI0_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi1_bus_clk, "twsi1_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI1_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi2_bus_clk, "twsi2_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI2_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi4_bus_clk, "twsi4_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI4_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi5_bus_clk, "twsi5_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI5_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi6_bus_clk, "twsi6_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI6_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi7_bus_clk, "twsi7_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI7_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(twsi8_bus_clk, "twsi8_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TWSI8_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(timers1_bus_clk, "timers1_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TIMERS1_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(timers2_bus_clk, "timers2_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TIMERS2_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(aib_bus_clk, "aib_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_AIB_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(onewire_bus_clk, "onewire_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_ONEWIRE_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(sspa0_bus_clk, "sspa0_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_SSPA0_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(sspa1_bus_clk, "sspa1_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_SSPA1_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(tsen_bus_clk, "tsen_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_TSEN_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(ipc_ap2aud_bus_clk, "ipc_ap2aud_bus_clk", CCU_PARENT_HW(apb_clk), > + APBC_IPC_AP2AUD_CLK_RST, > + BIT(0), BIT(0), 0x0, > + 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), > +}; OK I'm just going to point out that this is the first clock that defines a "FC" (frequency change) register/bit. It must be set by software when the clock control register is updated, and the hardware will clear that bit to signal that the change is complete. > +static CCU_DIV_FC_MUX_DEFINE(pmua_aclk, "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", 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", 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_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, "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, "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", 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), > +}; The following clock is named "cpu_c1_pclk" on the clock tree diagram. > +static CCU_MUX_FC_DEFINE(cpu_c1_core_clk, "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, "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_clk", jpg_parents, > + APMU_JPG_CLK_RES_CTRL, > + 5, 3, BIT(15), > + 2, 3, BIT(1), BIT(1), 0x0, > + 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_clk", ccic2phy_parents, > + APMU_CSI_CCIC2_CLK_RES_CTRL, > + 7, 1, BIT(5), BIT(5), 0x0, > + 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_clk", ccic3phy_parents, > + APMU_CSI_CCIC2_CLK_RES_CTRL, > + 31, 1, BIT(30), BIT(30), 0x0, > + 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_clk", csi_parents, > + APMU_CSI_CCIC2_CLK_RES_CTRL, > + 20, 3, BIT(15), > + 16, 3, BIT(4), BIT(4), 0x0, > + 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, "camm0_clk", camm_parents, > + APMU_CSI_CCIC2_CLK_RES_CTRL, > + 23, 4, 8, 2, > + BIT(28), BIT(28), 0x0, > + 0); > +static CCU_DIV_MUX_GATE_DEFINE(camm1_clk, "camm1_clk", camm_parents, > + APMU_CSI_CCIC2_CLK_RES_CTRL, > + 23, 4, 8, 2, BIT(6), BIT(6), 0x0, > + 0); > +static CCU_DIV_MUX_GATE_DEFINE(camm2_clk, "camm2_clk", camm_parents, > + APMU_CSI_CCIC2_CLK_RES_CTRL, > + 23, 4, 8, 2, BIT(3), BIT(3), 0x0, > + 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_clk", isp_cpp_parents, > + APMU_ISP_CLK_RES_CTRL, > + 24, 2, 26, 1, BIT(28), BIT(28), 0x0, > + 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_clk", isp_bus_parents, > + APMU_ISP_CLK_RES_CTRL, > + 18, 3, BIT(23), > + 21, 2, BIT(17), BIT(17), 0x0, > + 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_clk", isp_parents, > + APMU_ISP_CLK_RES_CTRL, > + 4, 3, BIT(7), > + 8, 2, BIT(1), BIT(1), 0x0, > + 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_DIV2_FC_MUX_GATE_DEFINE(dpu_mclk, "dpu_mclk", dpumclk_parents, > + APMU_LCD_CLK_RES_CTRL2, > + APMU_LCD_CLK_RES_CTRL1, > + 1, 4, BIT(29), > + 5, 3, BIT(0), BIT(0), 0x0, > + 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, "dpu_esc_clk", dpuesc_parents, > + APMU_LCD_CLK_RES_CTRL1, > + 0, 2, BIT(2), BIT(2), 0x0, > + 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), The next two parent clocks are duplicates. It looks this way on the clock tree diagram as well. Is this correct? Can you find out from SpacemiT whether one of them is actually a different clock (like pll2_d6 or something)? It makes no sense to have two multiplexed parent clocks with the same source. > + CCU_PARENT_HW(pll2_d8), > + CCU_PARENT_HW(pll2_d8), > +}; > +static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, "dpu_bit_clk", dpubit_parents, > + APMU_LCD_CLK_RES_CTRL1, > + 17, 3, BIT(31), > + 20, 3, BIT(16), BIT(16), 0x0, > + 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_DIV2_FC_MUX_GATE_DEFINE(dpu_pxclk, "dpu_pxclk", dpupx_parents, > + APMU_LCD_CLK_RES_CTRL2, > + APMU_LCD_CLK_RES_CTRL1, > + 17, 4, BIT(30), > + 21, 3, BIT(16), BIT(16), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(dpu_hclk, "dpu_hclk", CCU_PARENT_HW(pmua_aclk), > + APMU_LCD_CLK_RES_CTRL1, > + BIT(5), BIT(5), 0x0, > + 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_clk", dpu_spi_parents, > + APMU_LCD_SPI_CLK_RES_CTRL, > + 8, 3, BIT(7), > + 12, 3, BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(dpu_spi_hbus_clk, "dpu_spi_hbus_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_LCD_SPI_CLK_RES_CTRL, > + BIT(3), BIT(3), 0x0, > + 0); > +static CCU_GATE_DEFINE(dpu_spi_bus_clk, "dpu_spi_bus_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_LCD_SPI_CLK_RES_CTRL, > + BIT(5), BIT(5), 0x0, > + 0); > +static CCU_GATE_DEFINE(dpu_spi_aclk, "dpu_spi_aclk", CCU_PARENT_HW(pmua_aclk), > + APMU_LCD_SPI_CLK_RES_CTRL, > + BIT(6), BIT(6), 0x0, > + 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_clk", v2d_parents, > + APMU_LCD_CLK_RES_CTRL1, > + 9, 3, BIT(28), > + 12, 2, BIT(8), BIT(8), 0x0, > + 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_clk", ccic_4x_parents, > + APMU_CCIC_CLK_RES_CTRL, > + 18, 3, BIT(15), > + 23, 2, BIT(4), BIT(4), 0x0, > + 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_clk", ccic1phy_parents, > + APMU_CCIC_CLK_RES_CTRL, > + 7, 1, BIT(5), BIT(5), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(sdh_axi_aclk, "sdh_axi_aclk", CCU_PARENT_HW(pmua_aclk), > + APMU_SDH0_CLK_RES_CTRL, > + BIT(3), BIT(3), 0x0, > + 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, "sdh0_clk", sdh01_parents, > + APMU_SDH0_CLK_RES_CTRL, > + 8, 3, BIT(11), > + 5, 3, BIT(4), BIT(4), 0x0, > + 0); > +static CCU_DIV_FC_MUX_GATE_DEFINE(sdh1_clk, "sdh1_clk", sdh01_parents, > + APMU_SDH1_CLK_RES_CTRL, > + 8, 3, BIT(11), > + 5, 3, BIT(4), BIT(4), 0x0, > + 0); This is strange too. sdh2_parents is identical to sdh01_parents. All of the bits used are identical for the three control registers. The clock tree diagram shows all three of these sdhx clocks sharing a single parent mux. Why is sdh2_parents defined separately? > +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_clk", sdh2_parents, > + APMU_SDH2_CLK_RES_CTRL, > + 8, 3, BIT(11), > + 5, 3, BIT(4), BIT(4), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(usb_axi_clk, "usb_axi_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_USB_CLK_RES_CTRL, > + BIT(1), BIT(1), 0x0, > + 0); > +static CCU_GATE_DEFINE(usb_p1_aclk, "usb_p1_aclk", CCU_PARENT_HW(pmua_aclk), > + APMU_USB_CLK_RES_CTRL, > + BIT(5), BIT(5), 0x0, > + 0); > +static CCU_GATE_DEFINE(usb30_clk, "usb30_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_USB_CLK_RES_CTRL, > + BIT(8), BIT(8), 0x0, > + 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_clk", qspi_parents, > + APMU_QSPI_CLK_RES_CTRL, > + 9, 3, BIT(12), > + 6, 3, BIT(4), BIT(4), 0x0, > + 0); > +static CCU_GATE_DEFINE(qspi_bus_clk, "qspi_bus_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_QSPI_CLK_RES_CTRL, > + BIT(3), BIT(3), 0x0, > + 0); > +static CCU_GATE_DEFINE(dma_clk, "dma_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_DMA_CLK_RES_CTRL, > + BIT(3), BIT(3), 0x0, > + 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_clk", aes_parents, > + APMU_AES_CLK_RES_CTRL, > + 6, 1, BIT(5), BIT(5), 0x0, > + 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_clk", vpu_parents, > + APMU_VPU_CLK_RES_CTRL, > + 13, 3, BIT(21), > + 10, 3, > + BIT(3), BIT(3), 0x0, > + 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_clk", gpu_parents, > + APMU_GPU_CLK_RES_CTRL, > + 12, 3, BIT(15), > + 18, 3, > + BIT(4), BIT(4), 0x0, > + 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_clk", emmc_parents, > + APMU_PMUA_EM_CLK_RES_CTRL, > + 8, 3, BIT(11), > + 6, 2, > + BIT(4), BIT(4), 0x0, > + 0); > +static CCU_DIV_GATE_DEFINE(emmc_x_clk, "emmc_x_clk", CCU_PARENT_HW(pll1_d2_1228p8), > + APMU_PMUA_EM_CLK_RES_CTRL, > + 12, 3, BIT(15), BIT(15), 0x0, > + 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_clk", audio_parents, > + APMU_AUDIO_CLK_RES_CTRL, > + 4, 3, BIT(15), > + 7, 3, > + BIT(12), BIT(12), 0x0, > + 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_mclk", hdmi_parents, > + APMU_HDMI_CLK_RES_CTRL, > + 1, 4, BIT(29), > + 5, 3, > + BIT(0), BIT(0), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(pcie0_clk, "pcie0_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_PCIE_CLK_RES_CTRL_0, > + 0x7, 0x7, 0x0, > + 0); > +static CCU_GATE_DEFINE(pcie1_clk, "pcie1_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_PCIE_CLK_RES_CTRL_1, > + 0x7, 0x7, 0x0, > + 0); > +static CCU_GATE_DEFINE(pcie2_clk, "pcie2_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_PCIE_CLK_RES_CTRL_2, > + 0x7, 0x7, 0x0, > + 0); > + > +static CCU_GATE_DEFINE(emac0_bus_clk, "emac0_bus_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_EMAC0_CLK_RES_CTRL, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(emac0_ptp_clk, "emac0_ptp_clk", CCU_PARENT_HW(pll2_d6), > + APMU_EMAC0_CLK_RES_CTRL, > + BIT(15), BIT(15), 0x0, > + 0); > +static CCU_GATE_DEFINE(emac1_bus_clk, "emac1_bus_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_EMAC1_CLK_RES_CTRL, > + BIT(0), BIT(0), 0x0, > + 0); > +static CCU_GATE_DEFINE(emac1_ptp_clk, "emac1_ptp_clk", CCU_PARENT_HW(pll2_d6), > + APMU_EMAC1_CLK_RES_CTRL, > + BIT(15), BIT(15), 0x0, > + 0); > + > +static CCU_GATE_DEFINE(emmc_bus_clk, "emmc_bus_clk", CCU_PARENT_HW(pmua_aclk), > + APMU_PMUA_EM_CLK_RES_CTRL, > + BIT(3), BIT(3), 0, > + 0); > +/* APMU clocks end */ > + You could reduce some duplication here using a simple macro for defining the address of the clk_hw structure: #define CLK_HW(x) &x.common.hw (Add parentheses if you like.) It saves 3 characters for each clock... > +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { > + .hws = { > + [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, > + > + }, > + .num = CLK_APBS_NUM, It sure would be nice to be able to use ARRAY_SIZE() here but alas I don't think it's possible here. > +}; > + > +static struct clk_hw_onecell_data k1_ccu_mpmu_clks = { > + .hws = { > + [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_351] = &pll1_d7_351p08.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, > + }, > + .num = CLK_MPMU_NUM, > +}; > + > +static struct clk_hw_onecell_data k1_ccu_apbc_clks = { > + .hws = { > + [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, > + }, > + .num = CLK_APBC_NUM, > +}; > + > +static struct clk_hw_onecell_data k1_ccu_apmu_clks = { > + .hws = { > + [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] = &pcie0_clk.common.hw, > + [CLK_PCIE1] = &pcie1_clk.common.hw, > + [CLK_PCIE2] = &pcie2_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, > + }, > + .num = CLK_APMU_NUM > +}; > + The only time the "PLL lock" is needed is with PLL clocks. Can you encode this flag, or perhaps the address of the lock register, into the ccu_pll structure instead somehow? If so you wouldn't need this spacemit_ccu_data structure, and could simply use the clk_hw_onecell_data structure in its place. > +struct spacemit_ccu_data { > + struct clk_hw_onecell_data *hw_clks; > + bool need_pll_lock; > +}; > + You should get rid of the spacemit_ccu_priv structure. More below. > +struct spacemit_ccu_priv { > + const struct spacemit_ccu_data *data; > + struct regmap *base; > + struct regmap *lock_base; > +}; > + You use the fields in the above priv structure temporarily, to pass three values into spacemit_ccu_register(). Just pass those three values as arguments to this function, and get rid of the structure. > +static int spacemit_ccu_register(struct device *dev, > + struct spacemit_ccu_priv *priv) > +{ > + const struct spacemit_ccu_data *data = priv->data; You never use the data pointer, except when you're using its hw_clks field. So it would be better to do: struct clk_hw_onecell_data *hw_clks = &priv->data.hw_clks; Then use that. If you eliminated the spacemit_ccu_data type as I suggested above, you could just pass in the clk_hw_onecell_data pointer as an argument. > + int i, ret; > + > + for (i = 0; i < data->hw_clks->num; i++) { > + struct clk_hw *hw = data->hw_clks->hws[i]; I think the hw pointer should probably be const. There may be other spots that should be const as well. > + struct ccu_common *common; > + const char *name; Don't bother with the name variable. > + > + if (!hw) > + continue; > + > + common = hw_to_ccu_common(hw); > + name = hw->init->name; > + The next two pointers are regmap pointers. Could the name of the fields suggest that? (They sound like raw memory addresses to me.) > + common->base = priv->base; > + common->lock_base = priv->lock_base; > + > + ret = devm_clk_hw_register(dev, hw); > + if (ret) { > + dev_err(dev, "Cannot register clock %d - %s\n", > + i, name); Just use hw->init->name here instead of name. > + return ret; > + } > + } > + > + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, > + data->hw_clks); > +} > + > +static int k1_ccu_probe(struct platform_device *pdev) > +{ > + const struct spacemit_ccu_data *data; > + struct regmap *base_map, *lock_map = NULL; > + struct device *dev = &pdev->dev; > + struct spacemit_ccu_priv *priv; > + struct device_node *parent; > + int ret; > + > + data = of_device_get_match_data(dev); You can tell by inspection that data will never be null. So the next two lines can go away. > + if (WARN_ON(!data)) > + return -EINVAL; > + > + parent = of_get_parent(dev->of_node); > + base_map = syscon_node_to_regmap(parent); > + of_node_put(parent); > + > + if (IS_ERR(base_map)) > + return dev_err_probe(dev, PTR_ERR(base_map), > + "failed to get regmap\n"); > + > + if (data->need_pll_lock) { This is the weird part that requires the MPMU CCU to get fully probed before the APBC CCU can complete its probe. It would be nice if all the clocks shared their address ranges somehow to avoid this, but I'm not sure that maps well to the hardware. > + lock_map = syscon_regmap_lookup_by_phandle(dev->of_node, > + "spacemit,mpmu"); > + if (IS_ERR(lock_map)) > + return dev_err_probe(dev, PTR_ERR(lock_map), > + "failed to get lock regmap\n"); > + } > + You allocate the priv structure, but never free it (a bug). It only holds 3 values; just pass them as arguments when registering the CCU, as suggested above. > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->data = data; > + priv->base = base_map; > + priv->lock_base = lock_map; > + > + ret = spacemit_ccu_register(dev, priv); > + if (ret) > + return dev_err_probe(dev, ret, "failed to register clocks\n"); > + > + return 0; > +} > + > +static const struct spacemit_ccu_data k1_ccu_apbs_data = { > + .need_pll_lock = true, > + .hw_clks = &k1_ccu_apbs_clks, > +}; > + > +static const struct spacemit_ccu_data k1_ccu_mpmu_data = { > + .need_pll_lock = false, > + .hw_clks = &k1_ccu_mpmu_clks, > +}; > + > +static const struct spacemit_ccu_data k1_ccu_apbc_data = { > + .need_pll_lock = false, > + .hw_clks = &k1_ccu_apbc_clks, > +}; > + > +static const struct spacemit_ccu_data k1_ccu_apmu_data = { > + .need_pll_lock = false, > + .hw_clks = &k1_ccu_apmu_clks, > +}; > + > +static const struct of_device_id of_k1_ccu_match[] = { > + { > + .compatible = "spacemit,k1-ccu-apbs", > + .data = &k1_ccu_apbs_data, > + }, > + { > + .compatible = "spacemit,k1-ccu-mpmu", > + .data = &k1_ccu_mpmu_data, > + }, > + { > + .compatible = "spacemit,k1-ccu-apbc", > + .data = &k1_ccu_apbc_data, > + }, > + { > + .compatible = "spacemit,k1-ccu-apmu", > + .data = &k1_ccu_apmu_data, > + }, > + { } > +}; > +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"); End of "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..242461ee592f > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_common.h > @@ -0,0 +1,51 @@ > +/* 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 *base; > + struct regmap *lock_base; > + > + union { The following struct is used for all clocks except PLL. > + struct { > + u32 reg_ctrl; The reg_sel field is never needed and can go away. > + u32 reg_sel; > + u32 reg_fc; > + u32 fc; > + }; Add a comment indicating that the following struct is used only for PLL type clocks. > + struct { > + u32 reg_swcr1; The reg_swcr2 field is never needed and can go away. > + u32 reg_swcr2; > + u32 reg_swcr3; > + }; > + }; > + > + unsigned long flags; I don't believe the name field here is *ever* used, despite being assigned for every clock. The name is put into the hw.init structure, so there's no need for this one. > + const char *name; The parent_names field here is also not used. The names of the parent clocks are provided to the hw.init structure though. > + const char * const *parent_names; The num_parents field isn't necessary. It seems to be used when looping through parents in ccu_mix_calc_best_rate(), but I think the number can be looked up with clk_hw_get_num_parents() as is done in ccu_mux_get_parent(). > + int num_parents; > + > + 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); > +} > + Although I think the following macros are nice and concise, they obscure the fact that they're essentially renaming existing regmap operations. They don't really add much value; the regmap functions are pretty clear. Although regmap_read() returns a value, you never check for an error when calling ccu_read(). > +#define ccu_read(reg, c, val) regmap_read((c)->base, (c)->reg_##reg, val) The ccu_write() macro is never used. > +#define ccu_write(reg, c, val) regmap_write((c)->base, (c)->reg_##reg, val) Although regmap_update_bits() returns a value, you never check for an error when calling ccu_update(). > +#define ccu_update(reg, c, mask, val) \ > + regmap_update_bits((c)->base, (c)->reg_##reg, mask, val) You *do* look at the return value for ccu_poll()!!! > +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \ > + regmap_read_poll_timeout_atomic((c)->base, (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..1df555888ecb > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_ddn.c > @@ -0,0 +1,140 @@ > +// 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 "ccu_ddn.h" What does "DDN" stand for? > + > +/* > + * It is M/N clock > + * > + * Fout from synthesizer can be given from two equations: > + * numerator/denominator = Fin / (Fout * factor) > + */ The next three functions are never used and can be removed. The reason is that the only time they'd be used is when CCU_DDN_GATE_DEFINE() (defined in "ccu_ddn.h") is called, but it never is. So you can get rid of CCU_DDN_GATE_DEFINE(), and once you do that there's no need for ccu_ddn_disable(), ccu_ddn_enable(), and ccu_ddn_is_enabled(). Furthermore, these three functions are the only place the "reg_sel" field in the ccu_common structure is used, and without these functions, that field serves no purpose. So that field can go away, and there's no need to provide a value for initializing it in all those clock definition macros. > +static void ccu_ddn_disable(struct clk_hw *hw) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + struct ccu_common *common = &ddn->common; > + > + ccu_update(sel, common, ddn->gate, 0); > +} > + > +static int ccu_ddn_enable(struct clk_hw *hw) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + struct ccu_common *common = &ddn->common; > + > + ccu_update(sel, common, ddn->gate, ddn->gate); > + > + return 0; > +} > + > +static int ccu_ddn_is_enabled(struct clk_hw *hw) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + struct ccu_common *common = &ddn->common; > + u32 tmp; > + > + ccu_read(sel, common, &tmp); > + > + return tmp & ddn->gate; > +} > + Defining a ->determine_rate() is better than ->round_rate(), given you implement multiplexed parent clocks. > +static long clk_ddn_round_rate(struct clk_hw *hw, unsigned long drate, > + unsigned long *prate) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + struct ccu_ddn_config *params = &ddn->ddn; > + unsigned long rate = 0, prev_rate; > + unsigned long result; > + int i; > + > + for (i = 0; i < params->tbl_size; i++) { > + prev_rate = rate; I'm going to say more about this below, but I think the factor can be removed and hard-coded here. > + rate = (*prate * params->tbl[i].den) / > + (params->tbl[i].num * params->info->factor); I think you want this to use >= in the comparison. If the rate is *exactly* the desired rate, that's what you want to use. > + if (rate > drate) > + break; > + } > + I'm pretty sure the table size is always non-zero. And if so, the above loop can be a do...while, which I think would mean there's no need to to treat i == 0 specially. You still would want to return 0 if no entry is found. > + if ((i == 0) || (i == params->tbl_size)) { > + result = rate; > + } else { > + if ((drate - prev_rate) > (rate - drate)) > + result = rate; > + else > + result = prev_rate; > + } > + > + return result; > +} > + > +static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + struct ccu_ddn_config *params = &ddn->ddn; > + unsigned int val, num, den; > + unsigned long rate; > + > + ccu_read(ctrl, &ddn->common, &val); > + > + num = (val >> params->info->num_shift) & params->info->num_mask; > + den = (val >> params->info->den_shift) & params->info->den_mask; > + > + if (!den) > + return 0; > + Here too, I think the factor can be hard-coded. > + rate = (parent_rate * den) / (num * params->info->factor); > + > + return rate; > +} > + > +/* Configures new clock rate*/ > +static int clk_ddn_set_rate(struct clk_hw *hw, unsigned long drate, > + unsigned long prate) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + struct ccu_ddn_config *params = &ddn->ddn; > + struct ccu_ddn_info *info = params->info; > + unsigned long rate = 0; > + int i; > + > + for (i = 0; i < params->tbl_size; i++) { The way you calculate the output rate here tells me that your "num" and "den" field names are completely backward. The numerator is the top part of a fraction and the denominator is the bottom. output_rate = parent_rate * numerator / (denominator * factor) Please fix this naming (carefully). (And I propose hard-coding the factor here as well.) > + rate = (prate * params->tbl[i].den) / > + (params->tbl[i].num * info->factor); > + Here too, I think you want >=, not > in the comparison. > + if (rate > drate) > + break; > + } > + > + if (i > 0) > + i--; > + > + ccu_update(ctrl, &ddn->common, > + info->num_mask | info->den_mask, > + (params->tbl[i].num << info->num_shift) | > + (params->tbl[i].den << info->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, > +}; > + > +const struct clk_ops spacemit_ccu_ddn_gate_ops = { > + .disable = ccu_ddn_disable, > + .enable = ccu_ddn_enable, > + .is_enabled = ccu_ddn_is_enabled, > + .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..6fa658bcd50a > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_ddn.h > @@ -0,0 +1,84 @@ > +/* 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" > + This struct definition is fine, but I think the way you used the fields is completely wrong (as I said above). > +struct ccu_ddn_tbl { > + unsigned int num; > + unsigned int den; > +}; > + Maybe the factor is included here to somehow match the way the hardware works. In practice, the factor is an additional divider for the parent clock rate. The reality is there are only two DDN clocks, and both of them supply 2 for the factor. So rather than using a separate field to hold that value, just incorporate the additional factor of 2 where the rates are calculated in the code. Add a comment to explain if it helps. > +struct ccu_ddn_info { > + unsigned int factor; Here is a general comment that applies to a bunch of places in this code. You can concisely define a field with a single mask value. That means there is no need to define both a mask (or a field width) and a shift. What you need is defined in <linux/bitfield.h>. There you will find a set of macros like FIELD_GET() and FIELD_PREP(), or alternatively a set of inline functions like u32_get_bits() and u32_encode_bits(). > + unsigned int num_mask; > + unsigned int den_mask; > + unsigned int num_shift; > + unsigned int den_shift; > +}; So instead of defining this: static struct ccu_ddn_info uart_ddn_mask_info = { .factor = 2, .num_mask = 0x1fff, .den_mask = 0x1fff, .num_shift = 16, .den_shift = 0, }; You can define: static struct ccu_ddn_info uart_ddn_mask_info = { .factor = 2, .num_mask = 0x1fff0000, .den_mask = 0x00001fff, }; And then you use those masks with the helper functions to extract or encode values from or two fields. I'm not going to say more here about how to do that, but I'll point out some more places where this suggestion applies. > + > +struct ccu_ddn_config { > + struct ccu_ddn_info *info; > + struct ccu_ddn_tbl *tbl; > + u32 tbl_size; I mentioned this earlier. In the two DDN clocks defined, both have a single entry table. So rather than defining this with a poitner to a table and a table size, just define the num and den fields directly in this structure. > +}; > + > +struct ccu_ddn { > + struct ccu_ddn_config ddn; > + struct ccu_common common; > + u32 gate; > +}; > + > +#define CCU_DDN_CONFIG(_info, _table) \ > + { \ > + .info = (struct ccu_ddn_info *)_info, \ > + .tbl = (struct ccu_ddn_tbl *)&_table, \ > + .tbl_size = ARRAY_SIZE(_table), \ > + } > + > +#define CCU_DDN_INIT(_name, _parent, _ops, _flags) \ > + CLK_HW_INIT_HW(_name, &_parent.common.hw, &_ops, _flags) > + > +#define CCU_DDN_DEFINE(_struct, _name, _parent, _info, _table, \ > + _reg_ctrl, _flags) \ > + struct ccu_ddn _struct = { \ > + .ddn = CCU_DDN_CONFIG(_info, _table), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .hw.init = CCU_DDN_INIT(_name, _parent, \ > + spacemit_ccu_ddn_ops, \ > + _flags), \ > + } \ > + } > + As mentioned before, the following macro is never used and can be removed. That means a bunch of other things can be removed as well. > +#define CCU_DDN_GATE_DEFINE(_struct, _name, _parent, _info, _table, \ > + _reg_ddn, _reg_gate, _gate_mask, _flags) \ > + struct ccu_ddn _struct = { \ > + .ddn = CCU_DDN_CONFIG(_info, _table), \ > + .common = { \ > + .reg_ctrl = _reg_ddn, \ > + .reg_sel = _reg_gate, \ > + .hw.init = CCU_DDN_INIT(_name, _parent, \ > + &spacemit_ccu_ddn_gate_ops, \ > + _flags), \ > + } \ > + .gate = _gate_mask, \ > + } > + > +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, spacemit_ccu_ddn_gate_ops; > + > +#endif > diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c > new file mode 100644 > index 000000000000..b46eeb59faea > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_mix.c > @@ -0,0 +1,304 @@ > +// 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; > + struct ccu_gate_config *gate = mix->gate; > + I will explain more below. But gate->val_disable can go away and you should just use 0 here. > + ccu_update(ctrl, common, gate->gate_mask, gate->val_disable); > +} > + > +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; > + u32 val_enable, mask; > + u32 tmp; > + > + val_enable = gate->val_enable; > + mask = gate->gate_mask; > + > + ccu_update(ctrl, common, mask, val_enable); > + > + return ccu_poll(ctrl, common, tmp, (tmp & mask) == val_enable, > + 10, MIX_TIMEOUT); Here too, val_enable can just go away, and this can be simplified: return ccu_poll(ctrl, common, tmp, tmp & mask, 10, MIX_TIMEOUT); > +} > + > +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; > + struct ccu_gate_config *gate = mix->gate; > + u32 tmp; > + > + ccu_read(ctrl, common, &tmp); > + With val_enable gone, this would be: return !!(tmp & gate->gate_mask); > + return (tmp & gate->gate_mask) == gate->val_enable; > +} > + > +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, ®); > + > + val = reg >> div->shift; > + val &= (1 << div->width) - 1; > + > + val = divider_recalc_rate(hw, parent_rate, val, div->table, > + div->flags, 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); > + struct ccu_common *common = &mix->common; > + struct ccu_div_config *div = mix->div; > + u32 div_max = div ? 1 << div->width : 1; > + unsigned long best_rate = 0; > + > + for (int i = 0; i < common->num_parents; i++) { > + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); unsigned long parent_rate; > + > + if (!parent) > + continue; > + > + unsigned long parent_rate = clk_hw_get_rate(parent); 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; With my suggested change for using bitfields, this would be: current_div = u32_get_bits(tmp, div->mask); > + > + if (current_div == target_div) > + return 0; > + > + tmp = GENMASK(div->width + div->shift - 1, div->shift); And the above would not be required; just use div->mask (which I'll define a little later). > + > + 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, ®); > + > + parent = reg >> mux->shift; > + parent &= (1 << mux->width) - 1; > + > + if (mux->table) { > + int num_parents = clk_hw_get_num_parents(&common->hw); > + int i; > + > + for (i = 0; i < num_parents; i++) > + if (mux->table[i] == parent) > + return i; I think if there is no table entry found there should at least be a WARN() call or something. > + } > + > + 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; > + > + if (mux->table) You should verify that the index is less than the number of entries in the table before you blindly dereference it here. > + index = mux->table[index]; > + > + 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..97a3a099b42f > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_mix.h > @@ -0,0 +1,309 @@ > +/* 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 gate_mask; Get rid of the val_enable and val_disable definitions. In all cases, val_enable is equal to gate_mask (and in fact, there are a few gate masks made of more than a single bit). And in all cases, val_disable is zero. So just use gate_mask and 0 to represent enable and disable. > + u32 val_enable; > + u32 val_disable; > + u32 flags; When you have a flags field like this, it's helpful to add a comment that indicates what values it can take on. I believe in all cases like this, the flags values are standard clock framework flags. So what I mean is do something like this: u32 flags; /* E.g., CLK_IGNORE_UNUSED */ > +}; > + > +struct ccu_factor_config { > + u32 div; > + u32 mul; > +}; > + > +struct ccu_mux_config { > + const u8 *table; > + u32 flags; > + u8 shift; > + u8 width; The shift and width fields can be replaced with: u32 mask; Where: mask = ((1 << width) - 1) << shift; > +}; > + > +struct ccu_div_config { > + struct clk_div_table *table; > + u32 max; I'm pretty sure the offset field here is unused and should be removed. > + u32 offset; > + u32 flags; > + u8 shift; > + u8 width; The shift and width fields can be replaced with: u32 mask; > +}; > + > +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(_gate_mask, _val_enable, _val_disable, _flags) \ > + (&(struct ccu_gate_config) { \ > + .gate_mask = _gate_mask, \ > + .val_enable = _val_enable, \ > + .val_disable = _val_disable, \ > + .flags = _flags, \ > + }) > + > +#define CCU_FACTOR_INIT(_div, _mul) \ > + (&(struct ccu_factor_config) { \ > + .div = _div, \ > + .mul = _mul, \ > + }) > + > + Pass a mask rather than _shift and _width here, and initialize the structure accordingly. > +#define CCU_MUX_INIT(_shift, _width, _table, _flags) \ > + (&(struct ccu_mux_config) { \ > + .shift = _shift, \ > + .width = _width, \ > + .table = _table, \ > + .flags = _flags, \ > + }) > + Same here, use a mask rather than _shift and _width. > +#define CCU_DIV_INIT(_shift, _width, _table, _flags) \ > + (&(struct ccu_div_config) { \ > + .shift = _shift, \ > + .width = _width, \ > + .flags = _flags, \ > + .table = _table, \ > + }) > + > +#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) > + I'm almost certain that *all* calls to these *_DEFINE() macros pass the same name for the _struct value as the _name value, except the name is quoted. So use the C preprocessor here, and just pass the non-quoted thing, and use #_struct to stringify it. I'd rather the name of the argument be _name in that case. > +#define CCU_GATE_DEFINE(_struct, _name, _parent, _reg, _gate_mask, \ > + _val_enable, _val_disable, _flags) \ There is one case where the _flags value is CLK_IGNORE_UNUSED, and one case where it is CLK_IS_CRITICAL. All others pass 0. Given that, you could define two macros and remove the 0 argument from most of the callers: #define CCU_GATE_DEFINE_FLAGS(..., _flags) #define CCU_GATE_DEFINE(..., _val_disable) \ CCU_GATE_DEFINE_FLAGS(..., _val_disable, 0) > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .name = _name, \ > + .num_parents = 1, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_gate_ops, _flags), \ > + } \ > +} > + The _mul argument passed to CCU_FACTOR_DEFINE() is always 1. Just get rid of it, and use 1 for the value used in the macro. > +#define CCU_FACTOR_DEFINE(_struct, _name, _parent, _div, _mul) \ > +struct ccu_mix _struct = { \ > + .factor = CCU_FACTOR_INIT(_div, _mul), \ > + .common = { \ > + .name = _name, \ > + .num_parents = 1, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_factor_ops, 0), \ > + } \ > +} > + Same suggestion as above here, given that _flags is almost always 0. Also, pass a mask rather than _shift and _width here. (I'm not going to repeat this suggestion any more). > +#define CCU_MUX_DEFINE(_struct, _name, _parents, _reg, _shift, _width, \ > + _flags) \ > +struct ccu_mix _struct = { \ > + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_mux_ops, _flags),\ > + } \ > +} > + All three callers of this pass CLK_IS_CRITICAL for _flags. You could remove the argument and hard-code it, but I think you might want to change the name of the macro if so. > +#define CCU_DIV_DEFINE(_struct, _name, _parent, _reg, _shift, _width, \ > + _flags) \ > +struct ccu_mix _struct = { \ > + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .name = _name, \ > + .num_parents = 1, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_div_ops, _flags) \ > + } \ > +} > + > +#define CCU_GATE_FACTOR_DEFINE(_struct, _name, _parent, _reg, \ > + _gate_mask, _val_enable, _val_disable, \ You can get rid of _val_enable and _val_disable here; just use _gate_mask and 0 in their place. > + _div, _mul, _flags) \ > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .factor = CCU_FACTOR_INIT(_div, _mul), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .name = _name, \ > + .num_parents = 1, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_gate_factor_ops, _flags) \ > + } \ > +} > + > +#define CCU_MUX_GATE_DEFINE(_struct, _name, _parents, _reg, _shift, \ > + _width, _gate_mask, _val_enable, \ Get rid of _val_enable and _val_disable here too. (I'm not going to repeat this suggestion any more.) > + _val_disable, _flags) \ > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_mux_gate_ops, \ > + _flags), \ > + } \ > +} > + > +#define CCU_DIV_GATE_DEFINE(_struct, _name, _parent, _reg, _shift, \ > + _width, _gate_mask, _val_enable, \ > + _val_disable, _flags) \ > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .name = _name, \ > + .num_parents = 1, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_div_gate_ops, _flags), \ > + } \ > +} > + > +#define CCU_DIV_MUX_GATE_DEFINE(_struct, _name, _parents, _reg_ctrl, \ > + _mshift, _mwidth, _muxshift, _muxwidth, \ > + _gate_mask, _val_enable, _val_disable, \ > + _flags) \ > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_gate_ops,\ > + _flags), \ > + }, \ > +} > + I'd put the "2" somewhere else in the name of this. It suggests "divide by two" but it's really a "second DIV_FC_MUX_GATE" macro. > +#define CCU_DIV2_FC_MUX_GATE_DEFINE(_struct, _name, _parents, \ > + _reg_ctrl, _reg_fc, _mshift, \ > + _mwidth, _fc, _muxshift, _muxwidth, \ > + _gate_mask, _val_enable, \ > + _val_disable, _flags) \ > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .reg_fc = _reg_fc, \ > + .fc = _fc, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_gate_ops,\ > + _flags), \ > + }, \ > +} > + > +#define CCU_DIV_FC_MUX_GATE_DEFINE(_struct, _name, _parents, _reg_ctrl, \ > + _mshift, _mwidth, _fc, _muxshift, \ > + _muxwidth, _gate_mask, _val_enable, \ > + _val_disable, _flags) \ The only difference between this and CCU_DIV2_...() above is that the other macro takes a _reg_fc argument. Just pass _reg_ctrl for that, and define this macro in terms of the other one. > +struct ccu_mix _struct = { \ > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > + _val_disable, 0), \ > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .reg_fc = _reg_ctrl, \ > + .fc = _fc, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_gate_ops,\ > + _flags), \ > + }, \ > +} > + > +#define CCU_DIV_FC_MUX_DEFINE(_struct, _name, _parents, _reg_ctrl, \ > + _mshift, _mwidth, _fc, _muxshift, \ > + _muxwidth, _flags) \ > +struct ccu_mix _struct = { \ > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .reg_fc = _reg_ctrl, \ > + .fc = _fc, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_ops, \ > + _flags), \ > + }, \ > +} > + > +#define CCU_MUX_FC_DEFINE(_struct, _name, _parents, _reg_ctrl, _fc, \ > + _muxshift, _muxwidth, _flags) \ > +struct ccu_mix _struct = { \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .reg_fc = _reg_ctrl, \ > + .fc = _fc, \ > + .name = _name, \ > + .num_parents = ARRAY_SIZE(_parents), \ > + .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..81b929ca1c5c > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_pll.c > @@ -0,0 +1,189 @@ > +// 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_MIN_FREQ 600000000 > +#define PLL_MAX_FREQ 3400000000 > +#define PLL_DELAY_TIME 3000 > + These four values are 1-byte components of what gets placed in the SWCR1 register, to manage the PLL clocks. They all represent various things that configure the clock. But in fact, this code doesn't care at all about the format. All that matters is that certain combinations of them are well-defined, and produce a specific clock rate out of the PLL. For that reason, I recommend you just do away with this whole notion of the four bytes being defined separately. Just combine them all into a single 4-byte value that's sort of an abstract magic number. Then all of these definitions here can go away. I'll explain more below. > +#define PLL_SWCR1_REG5_OFF 0 > +#define PLL_SWCR1_REG5_MASK GENMASK(7, 0) > +#define PLL_SWCR1_REG6_OFF 8 > +#define PLL_SWCR1_REG6_MASK GENMASK(15, 8) > +#define PLL_SWCR1_REG7_OFF 16 > +#define PLL_SWCR1_REG7_MASK GENMASK(23, 16) > +#define PLL_SWCR1_REG8_OFF 24 > +#define PLL_SWCR1_REG8_MASK GENMASK(31, 24) > + The SWCR2_* macros can all go away; they're never used (or needed). > +#define PLL_SWCR2_DIVn_EN(n) BIT(n + 1) > +#define PLL_SWCR2_ATEST_EN BIT(12) > +#define PLL_SWCR2_CKTEST_EN BIT(13) > +#define PLL_SWCR2_DTEST_EN BIT(14) > + Here too, the "frc" and "int" parts of this register are ignored by this code. The only thing that matters is that both match a particular value, and if they do, then they take part in the production of a specific PLL output rate. So here too I suggest combining them into a single, 31-bit abstract magic value. The only other thing that matters in the SWCR3 register is the enable bit. > +#define PLL_SWCR3_DIV_FRC_OFF 0 > +#define PLL_SWCR3_DIV_FRC_MASK GENMASK(23, 0) > +#define PLL_SWCR3_DIV_INT_OFF 24 > +#define PLL_SWCR3_DIV_INT_MASK GENMASK(30, 24) > +#define PLL_SWCR3_EN BIT(31) > + This should be a Boolean function. > +static int ccu_pll_is_enabled(struct clk_hw *hw) > +{ > + struct ccu_pll *p = hw_to_ccu_pll(hw); You could probably do something like hw_to_ccu_comm(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) > +{ > + unsigned int reg5, reg6, reg7, reg8, size, i; > + unsigned int div_int, div_frc; > + struct ccu_pll_rate_tbl *freq_pll_regs_table; > + struct ccu_pll *p = hw_to_ccu_pll(hw); > + struct ccu_common *common = &p->common; > + u32 tmp; > + > + ccu_read(swcr1, common, &tmp); > + reg5 = (tmp & PLL_SWCR1_REG5_MASK) >> PLL_SWCR1_REG5_OFF; > + reg6 = (tmp & PLL_SWCR1_REG6_MASK) >> PLL_SWCR1_REG6_OFF; > + reg7 = (tmp & PLL_SWCR1_REG7_MASK) >> PLL_SWCR1_REG7_OFF; > + reg8 = (tmp & PLL_SWCR1_REG8_MASK) >> PLL_SWCR1_REG8_OFF; Here you're extracting the four bytes out of the value read from the SWCR1 register. > + > + ccu_read(swcr3, common, &tmp); And here you're extracting the INT and FRC parts of the value read from the SWCR3 register. > + div_int = (tmp & PLL_SWCR3_DIV_INT_MASK) >> PLL_SWCR3_DIV_INT_OFF; > + div_frc = (tmp & PLL_SWCR3_DIV_FRC_MASK) >> PLL_SWCR3_DIV_FRC_OFF; > + > + freq_pll_regs_table = p->pll.rate_tbl; > + size = p->pll.tbl_size; > + > + for (i = 0; i < size; i++) Then the only thing that matters is whether all four values in the table entry are equal to the four extracted values, *and* the div and frc values match as well. Just do a single 4-byte comparision of the value read from SWCR1, and also a comparison of the value read from SWCR3 (with the enable bit masked off). > + if ((freq_pll_regs_table[i].reg5 == reg5) && > + (freq_pll_regs_table[i].reg6 == reg6) && > + (freq_pll_regs_table[i].reg7 == reg7) && > + (freq_pll_regs_table[i].reg8 == reg8) && > + (freq_pll_regs_table[i].div_int == div_int) && > + (freq_pll_regs_table[i].div_frac == div_frc)) > + return freq_pll_regs_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_base, > + 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); > +} > + > +/* > + * pll rate change requires sequence: > + * clock off -> change rate setting -> clock on > + * This function doesn't really change rate, but cache the config Actually, the function *does* seem to change the rate. (Fix this comment.) > + */ > +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; > + struct ccu_pll_rate_tbl *entry = NULL; > + u32 mask, val; > + int i; > + > + for (i = 0; i < params->tbl_size; i++) { > + if (rate == params->rate_tbl[i].rate) { > + entry = ¶ms->rate_tbl[i]; > + break; > + } > + } > + > + if (WARN_ON_ONCE(!entry)) > + return -EINVAL; > + > + mask = PLL_SWCR1_REG5_MASK | PLL_SWCR1_REG6_MASK; > + mask |= PLL_SWCR1_REG7_MASK | PLL_SWCR1_REG8_MASK; > + val = entry->reg5 << PLL_SWCR1_REG5_OFF; > + val |= entry->reg6 << PLL_SWCR1_REG6_OFF; > + val |= entry->reg7 << PLL_SWCR1_REG7_OFF; > + val |= entry->reg8 << PLL_SWCR1_REG8_OFF; > + ccu_update(swcr1, common, mask, val); > + > + mask = PLL_SWCR3_DIV_INT_MASK | PLL_SWCR3_DIV_FRC_MASK; > + val = entry->div_int << PLL_SWCR3_DIV_INT_OFF; > + val |= entry->div_frac << PLL_SWCR3_DIV_FRC_OFF; > + ccu_update(swcr3, common, mask, val); > + > + 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 long max_rate = 0; unsigned long long max_rate = 0; (At least that's how it's defined in the ccu_pll_rate_tbl structure.) > + unsigned int i; > + > + for (i = 0; i < params->tbl_size; i++) { > + if (params->rate_tbl[i].rate <= rate) { > + if (max_rate < params->rate_tbl[i].rate) > + max_rate = params->rate_tbl[i].rate; > + } > + } > + I don't like that you're passing a compile-time defined frequency (which might not match reality). > + return MAX(max_rate, PLL_MIN_FREQ); > +} > + > +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..d3397120414e > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_pll.h > @@ -0,0 +1,80 @@ > +/* 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 { I'd prefer you define this as u64. But if you meant to match what the clock routines use, I think that's unsigned long (not long long). > + unsigned long long rate; > + u32 reg5; > + u32 reg6; > + u32 reg7; > + u32 reg8; > + unsigned int div_int; > + unsigned int div_frac; > +}; > + > +struct ccu_pll_config { The rate_tbl is probably const, isn't it? > + struct ccu_pll_rate_tbl *rate_tbl; > + u32 tbl_size; > + u32 reg_lock; > + u32 lock_enable_bit; > +}; > + Here I recommend you define two combined magic values rather than all these arguments. If reg5-reg8 are 0x67, 0xdd, 0x50, 0x00, then pass 0x0050dd67. And if div_int is 0x43 and div_frac is 0xeaaaab, pass 0x43eaaaab. > +#define CCU_PLL_RATE(_rate, _reg5, _reg6, _reg7, _reg8, _div_int, _div_frac) \ > + { \ > + .rate = (_rate), \ > + .reg5 = (_reg5), \ > + .reg6 = (_reg6), \ > + .reg7 = (_reg7), \ > + .reg8 = (_reg8), \ > + .div_int = (_div_int), \ > + .div_frac = (_div_frac), \ > + } > + > +struct ccu_pll { > + struct ccu_pll_config pll; > + struct ccu_common common; > +}; > + > +#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \ > + { \ It shouldn't be necessary to type-cast the table here. I think I saw this sort of thing elsewhere and didn't comment on it (and if that's true, fix those too). That's the end! -Alex > + .rate_tbl = (struct ccu_pll_rate_tbl *)&(_table), \ > + .tbl_size = ARRAY_SIZE(_table), \ > + .reg_lock = (_reg_lock), \ > + .lock_enable_bit = (_lock_enable_bit), \ > + } > + > +#define CCU_PLL_HWINIT(_name, _flags) \ > + CLK_HW_INIT_NO_PARENT(_name, &spacemit_ccu_pll_ops, _flags) > + > +#define CCU_PLL_DEFINE(_struct, _name, _table, _reg_swcr1, _reg_swcr2, \ > + _reg_swcr3, _reg_lock, _lock_enable_bit, _flags) \ > + \ > + struct ccu_pll _struct = { \ > + .pll = CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit), \ > + .common = { \ > + .reg_swcr1 = _reg_swcr1, \ > + .reg_swcr2 = _reg_swcr2, \ > + .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 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-02-14 4:04 ` Alex Elder @ 2025-02-16 11:34 ` Haylen Chu 2025-02-21 21:10 ` Alex Elder 2025-02-22 9:40 ` Haylen Chu 2025-03-03 9:41 ` Haylen Chu 1 sibling, 2 replies; 31+ messages in thread From: Haylen Chu @ 2025-02-16 11:34 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, Inochi Amaoto, Chen Wang, Guodong Xu On Thu, Feb 13, 2025 at 10:04:10PM -0600, Alex Elder wrote: > On 1/3/25 3:56 PM, Haylen Chu wrote: > > The clock tree of K1 SoC contains three main types of clock hardware > > (PLL/DDN/MIX) and is managed by several independent controllers in > > different SoC parts (APBC, APBS and etc.), thus different compatible > > strings are added to distinguish them. > > > > Some controllers may share IO region with reset controller and other low > > speed peripherals like watchdog, so all register operations are done > > through regmap to avoid competition. > > > > Signed-off-by: Haylen Chu <heylenay@4d2.org> > > This is a really big patch (over 3000 lines), and a fairly large > amount of code to review. But I've given it a really thorough > read and I have a *lot* of review comments for you to consider. Thanks for the detailed review! > First, a few top-level comments. > - This driver is very comprehensive. It represents essentially > *all* of the clocks in the tree diagram shown here: > https://developer.spacemit.com/resource/file/images?fileName=DkWGb4ed7oAziVxE6PIcbjTLnpd.png > (I can tell you what's missing but I don't think it matters.) > - In almost all cases, the names of the clocks match the names > shown in that diagram, which is very helpful. > - All of the clocks are implemented using "custom" clock > implementations. I'm fairly certain that almost all of > them can use standard clock framework types instead > (fixed-rate, fixed-factor, fractional-divider, mux, and > composite). But for now I think there are other things > more important to improve. These three types of clocks are originally adapted from the vendor driver, although the mix type has been largely rewritten during the revisions. If the custom types don't cause big problems, I'd like to see the driver merged first and we can gradually convert them to the more general ones, which avoids blocking other SoC drivers. > - A great deal of my commentary below is simply saying that the > code is more complex than necessary. Some simple (though > widespread) refactoring would improve things a lot. And > some of the definitions can be done without having to > specify nearly so many values. > - Much of what might be considered generality in the > implementation actually isn't needed, because it isn't used. > This is especially true given that there are essentially no > clocks left unspecified for the K1 SoC. > - Once the refactoring I suggest has been done, I expect > that more opportunities for simplification and cleanup will > become obvious; we'll see. > - I suggest these changes because the resulting simplicity > will make the code much more understandable and maintainable > in the long term. And if it's simpler to understand, it > should be easier for a maintainer to accept. > > I'm not going to comment on the things related to Device Tree > that have already been mentioned, nor on the Makefile or Kconfig, > etc. I'm focusing just on the code. > > > --- > > drivers/clk/Kconfig | 1 + > > drivers/clk/Makefile | 1 + > > drivers/clk/spacemit/Kconfig | 20 + > > drivers/clk/spacemit/Makefile | 5 + > > drivers/clk/spacemit/ccu-k1.c | 1747 +++++++++++++++++++++++++++++ > > drivers/clk/spacemit/ccu_common.h | 51 + > > drivers/clk/spacemit/ccu_ddn.c | 140 +++ > > drivers/clk/spacemit/ccu_ddn.h | 84 ++ > > drivers/clk/spacemit/ccu_mix.c | 304 +++++ > > drivers/clk/spacemit/ccu_mix.h | 309 +++++ > > drivers/clk/spacemit/ccu_pll.c | 189 ++++ > > drivers/clk/spacemit/ccu_pll.h | 80 ++ > > 12 files changed, 2931 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..6fb0a12ec261 > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu-k1.c > > @@ -0,0 +1,1747 @@ > > +// 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/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 */ > > > What do APBS, MPMU, APBC, and APMU stand for and represent? APMU and MPMU are abbreviated from Application/Main Power Management Unit, as mentioned earlier in my reply to the dt-binding things. APBC means APB Bus Clock Unit. APBS stands for APB SPARE and I'm not sure about the precise meaning. These information seems important but I'm not sure about where they should be documented. I could mention it in the cover letter or commit message in next version. > It seems each has its own clock controller, but can you > offer any explanation about why they're divided that way? Sadly no, I have no idea why the clock bits are divided into several MMIO regions, which really complicates binding. > > +/* pll1 */ > > These first few registers are found in the "APB_SPARE" space. > But what they represent are PLL control registers. I'd rather > have these take on their more meaningful names, like: > PLL1_SW1_CTRL > PLL1_SW3_CTRL > PLL2_SW1_CTRL, etc. Thanks, it makes sense. > > +#define APB_SPARE1_REG 0x100 > > +#define APB_SPARE2_REG 0x104 > > The above register, as well as APB_SPARE8_REG and APB_SPARE11_REG > below, are not actually used. More on this later. > > > +#define APB_SPARE3_REG 0x108 > > +/* pll2 */ > > +#define APB_SPARE7_REG 0x118 > > +#define APB_SPARE8_REG 0x11c > > +#define APB_SPARE9_REG 0x120 > > +/* pll3 */ > > +#define APB_SPARE10_REG 0x124 > > +#define APB_SPARE11_REG 0x128 > > +#define APB_SPARE12_REG 0x12c > > + > > +/* MPMU register offset */ > > +#define MPMU_POSR 0x10 > > This register is used to signal when enabling a PLL has completed, > with the 3 bits defined next corresponding to the three PLLs. > This is OK, but it seems weird that this register is in the MPMU > space, while the registers controlling the clocks themselves are > in APBC space. It makes the probe a little more complicated, > because it requires the MPMU controller probe to complete before > the APBC probe can. > > I guess that's not really a question or suggestion, it's an > oddity I wanted to point out. If you have any more information > about this, please share it. Sadly no, either. While searching through the clock bindings, I occasionally found there're similar split PLL status in Rockchip SoCs. Thus I think it's just some (probably wrong) decisions during hardware designing. > > +#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 > > The APBC_COUNTER_CLK_SEL register offset is never used. > > > +#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_CCI550_CLK_CTRL 0x300 > > +#define APMU_CPU_C0_CLK_CTRL 0x38C > > +#define APMU_CPU_C1_CLK_CTRL 0x390 > > +#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 > > APMU_USB_CLK_RES_CTRL is duplicated below. Thanks, will remove one along with other duplication. For the unused offsets, I prefer to keep the documented ones for future completion of the driver. I don't think it's a large burden. > > +#define APMU_USB_CLK_RES_CTRL 0x5c > > +#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 > > APMU_PMUA_MC_CTRL is never used, nor is APMU_PMU_CC2_AP. > > > +#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 > > APMU_CCI550_CLK_CTRL is duplicated below, as are APMU_ACLK_CLK_CTRL, > APMU_CPU_C0_CLK_CTRL, and APMU_CPU_C1_CLK_CTRL. > > > +#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 */ > > I'm going to say more about this below, but these definitions can > be simplified a lot. For example, the first one could be: > CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd), > > > +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = { > > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > > +}; > > + > > +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { > > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > > +}; > > + > > +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { > > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > > + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), > > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > > +}; > > + > > Therre are only three PLL clocks, defined next. All share the same > lock register offset (MPMU_POSR), so that could be factored out. I'm not sure how to factor the offset out, could you please explain more about it? By coding the offset in the macro or something else? I'd like to keep it as is, since PLL status in future SpacemiT SoCs are likely to locate in different registers. Factoring the offset out won't save a lot of characters, either. > > +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, > > + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, > > + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); > > +static CCU_PLL_DEFINE(pll2, "pll2", pll2_rate_tbl, > > + APB_SPARE7_REG, APB_SPARE8_REG, APB_SPARE9_REG, > > + MPMU_POSR, POSR_PLL2_LOCK, CLK_SET_RATE_GATE); > > +static CCU_PLL_DEFINE(pll3, "pll3", pll3_rate_tbl, > > + APB_SPARE10_REG, APB_SPARE11_REG, APB_SPARE12_REG, > > + MPMU_POSR, POSR_PLL3_LOCK, 0); > > + > > The next set of clocks differ from essentially all others, in that > they don't encode their frequency in the name. I.e., I would expect > the first one to be named pll1_d2_1228p8. Thanks, will adapt it in the next revision. > > +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d11_223p4, "pll1_d11_223p4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(15), BIT(15), 0, 11, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d13_189, "pll1_d13_189", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(16), BIT(16), 0, 13, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d23_106p8, "pll1_d23_106p8", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(20), BIT(20), 0, 23, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d64_38p4, "pll1_d64_38p4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(0), BIT(0), 0, 64, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_245p7, "pll1_aud_245p7", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(10), BIT(10), 0, 10, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_aud_24p5, "pll1_aud_24p5", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(11), BIT(11), 0, 100, 1, 0); > > + > > +static CCU_GATE_FACTOR_DEFINE(pll2_d1, "pll2_d1", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(0), BIT(0), 0, 1, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d2, "pll2_d2", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d3, "pll2_d3", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d4, "pll2_d4", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d5, "pll2_d5", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d6, "pll2_d6", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d7, "pll2_d7", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll2_d8, "pll2_d8", CCU_PARENT_HW(pll2), > > + APB_SPARE8_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > + > > +static CCU_GATE_FACTOR_DEFINE(pll3_d1, "pll3_d1", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(0), BIT(0), 0, 1, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d2, "pll3_d2", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d3, "pll3_d3", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d4, "pll3_d4", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d5, "pll3_d5", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d6, "pll3_d6", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d7, "pll3_d7", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll3_d8, "pll3_d8", CCU_PARENT_HW(pll3), > > + APB_SPARE11_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > + > > +static CCU_FACTOR_DEFINE(pll3_20, "pll3_20", CCU_PARENT_HW(pll3_d8), 20, 1); > > +static CCU_FACTOR_DEFINE(pll3_40, "pll3_40", CCU_PARENT_HW(pll3_d8), 10, 1); > > +static CCU_FACTOR_DEFINE(pll3_80, "pll3_80", CCU_PARENT_HW(pll3_d8), 5, 1); > > + > > +/* APBS clocks end */ > > + > > +/* MPMU clocks start */ > > +static CCU_GATE_DEFINE(pll1_d8_307p2, "pll1_d8_307p2", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(13), BIT(13), 0, 0); > > +static CCU_FACTOR_DEFINE(pll1_d32_76p8, "pll1_d32_76p8", CCU_PARENT_HW(pll1_d8_307p2), > > + 4, 1); > > +static CCU_FACTOR_DEFINE(pll1_d40_61p44, "pll1_d40_61p44", CCU_PARENT_HW(pll1_d8_307p2), > > + 5, 1); > > +static CCU_FACTOR_DEFINE(pll1_d16_153p6, "pll1_d16_153p6", CCU_PARENT_HW(pll1_d8), > > + 2, 1); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d24_102p4, "pll1_d24_102p4", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(12), BIT(12), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2, "pll1_d48_51p2", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(7), BIT(7), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d48_51p2_ap, "pll1_d48_51p2_ap", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(11), BIT(11), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_m3d128_57p6, "pll1_m3d128_57p6", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(8), BIT(8), 0, 16, 3, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d96_25p6, "pll1_d96_25p6", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(4), BIT(4), 0, 12, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8, "pll1_d192_12p8", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(3), BIT(3), 0, 24, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d192_12p8_wdt, "pll1_d192_12p8_wdt", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(19), BIT(19), 0x0, 24, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d384_6p4, "pll1_d384_6p4", CCU_PARENT_HW(pll1_d8), > > + MPMU_ACGR, > > + BIT(2), BIT(2), 0, 48, 1, 0); > > +static CCU_FACTOR_DEFINE(pll1_d768_3p2, "pll1_d768_3p2", CCU_PARENT_HW(pll1_d384_6p4), > > + 2, 1); > > +static CCU_FACTOR_DEFINE(pll1_d1536_1p6, "pll1_d1536_1p6", CCU_PARENT_HW(pll1_d384_6p4), > > + 4, 1); > > +static CCU_FACTOR_DEFINE(pll1_d3072_0p8, "pll1_d3072_0p8", CCU_PARENT_HW(pll1_d384_6p4), > > + 8, 1); > > + > > The next clock is weird, and it's the only one of its kind. It is not > represented in the clock tree diagram. It is a "factor 1" clock (so it > just passes the parent's rate through), and has no gate. Do you know > why it's defined? It is used only as one of the MPMU parent clocks. > Why isn't just the pll1_d7 clock used in its place? It is represented in the diagram. The photo version of the diagram seems hard to search so I will ask the vendor to publish a PDF version if possible. As the definition involves no hardware bits, I guess it's actually an alias listed to keep the tree structure in similar form. Will confirm this with the vendor. > > +static CCU_FACTOR_DEFINE(pll1_d7_351p08, "pll1_d7_351p08", CCU_PARENT_HW(pll1_d7), > > + 1, 1); > > + > > +static CCU_GATE_DEFINE(pll1_d6_409p6, "pll1_d6_409p6", CCU_PARENT_HW(pll1_d6), > > + MPMU_ACGR, > > + BIT(0), BIT(0), 0, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d12_204p8, "pll1_d12_204p8", CCU_PARENT_HW(pll1_d6), > > + MPMU_ACGR, > > + BIT(5), BIT(5), 0, 2, 1, 0); > > + > > +static CCU_GATE_DEFINE(pll1_d5_491p52, "pll1_d5_491p52", CCU_PARENT_HW(pll1_d5), > > + MPMU_ACGR, > > + BIT(21), BIT(21), 0, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d10_245p76, "pll1_d10_245p76", CCU_PARENT_HW(pll1_d5), > > + MPMU_ACGR, > > + BIT(18), BIT(18), 0, 2, 1, 0); > > + > > +static CCU_GATE_DEFINE(pll1_d4_614p4, "pll1_d4_614p4", CCU_PARENT_HW(pll1_d4), > > + MPMU_ACGR, > > + BIT(15), BIT(15), 0, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d52_47p26, "pll1_d52_47p26", CCU_PARENT_HW(pll1_d4), > > + MPMU_ACGR, > > + BIT(10), BIT(10), 0, 13, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d78_31p5, "pll1_d78_31p5", CCU_PARENT_HW(pll1_d4), > > + MPMU_ACGR, > > + BIT(6), BIT(6), 0, 39, 2, 0); > > + > > +static CCU_GATE_DEFINE(pll1_d3_819p2, "pll1_d3_819p2", CCU_PARENT_HW(pll1_d3), > > + MPMU_ACGR, > > + BIT(14), BIT(14), 0, 0); > > + > > +static CCU_GATE_DEFINE(pll1_d2_1228p8, "pll1_d2_1228p8", CCU_PARENT_HW(pll1_d2), > > + MPMU_ACGR, > > + BIT(16), BIT(16), 0, 0); > > +static struct ccu_ddn_info uart_ddn_mask_info = { > > + .factor = 2, > > + .num_mask = 0x1fff, > > + .den_mask = 0x1fff, > > + .num_shift = 16, > > + .den_shift = 0, > > +}; > > I think ccu_ddn_tbl structure can go away. More below. > > > +static struct ccu_ddn_tbl slow_uart1_tbl[] = { > > + { .num = 125, .den = 24 }, > > +}; > > +static struct ccu_ddn_tbl slow_uart2_tbl[] = { > > + { .num = 6144, .den = 960 }, > > +}; > > I'll note here that this "slow_uart" gate is defined separately > from the two slow UART DDN clocks below. That's different from > some other gated clocks. "slow_uart" is a gate that controlls both slow_uart1_14p7 and slow_uart2_48. Enabling any one of these two clocks requires slow_uart to ungate. I didn't find a good method to describe the structure under CCF (I'm really new to it), thus listed it out and applied the CLK_IGNORE_UNUSED trick. Any suggesions on this will be appreciated, thanks. > > +static CCU_GATE_DEFINE(slow_uart, "slow_uart", CCU_PARENT_NAME(osc), > > + MPMU_ACGR, > > + BIT(1), BIT(1), 0, CLK_IGNORE_UNUSED); > > What follows are the only two DDN clocks defined. Both define > a "table" of numerator/denominator pairs, but in both cases, > the table has only one entry. > > Given that, why not simply encode the numerator and denominator > in the ccu_info structure for each of these DDN clock instances? DDN types are M/N clocks, just like clk-fractional-divider. Hardcoded tables (even with only one entry) work because their only consumers are UARTs, which require a relatively fixed frequency. This will prevent us from generating precise clock if we want the UART to operate under a different baudrate. I'll cover more about this later. > > +static CCU_DDN_DEFINE(slow_uart1_14p74, "slow_uart1_14p74", pll1_d16_153p6, > > + &uart_ddn_mask_info, slow_uart1_tbl, > > + MPMU_SUCCR, 0); > > +static CCU_DDN_DEFINE(slow_uart2_48, "slow_uart2_48", pll1_d4_614p4, > > + &uart_ddn_mask_info, slow_uart2_tbl, > > + MPMU_SUCCR_1, 0); > > + > > +static CCU_GATE_DEFINE(wdt_clk, "wdt_clk", CCU_PARENT_HW(pll1_d96_25p6), > > + MPMU_WDTPCR, > > + BIT(1), BIT(1), 0x0, > > + 0); > > + > > I couldn't find the "ripc_clk" on the clock tree diagram. It is > never used elsewhere, so I think this definition can go away. I'm not sure whether the ripc_clk doesn't exist or it's just missing in both datasheet and clock tree diagram. Will confirm with the vendor. > > +static CCU_GATE_DEFINE(ripc_clk, "ripc_clk", CCU_PARENT_NAME(vctcxo_24m), > > + MPMU_RIPCCR, > > + 0x3, 0x3, 0x0, > > + 0); > > + > > +static CCU_GATE_FACTOR_DEFINE(i2s_sysclk, "i2s_sysclk", CCU_PARENT_HW(pll1_d16_153p6), > > + MPMU_ISCCR, > > + BIT(31), BIT(31), 0x0, 50, 1, > > + 0); > > +static CCU_GATE_FACTOR_DEFINE(i2s_bclk, "i2s_bclk", CCU_PARENT_HW(i2s_sysclk), > > + MPMU_ISCCR, > > + BIT(29), BIT(29), 0x0, 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_clk", apb_parents, > > + MPMU_APBCSCR, > > + 0, 2, > > + 0); > > + > > The following clock is just called "wdt_clk" on the clock tree diagram. They're different things. The wdt_clk is defined a few lines earlier and acts as the function clock of watchdog. Instead, wdt_bus_clk is its bus clock. Most bus clocks in the clock tree diagram aren't listed explicitly, but represented as a gate besides the function clock. > > +static CCU_GATE_DEFINE(wdt_bus_clk, "wdt_bus_clk", CCU_PARENT_HW(apb_clk), > > + MPMU_WDTPCR, > > + BIT(2), BIT(2), 0x0, > > + 0); I do find a typo here. BIT(0) should be used here instead of BIT(2), which is the reset bit for watchdog. > > +/* 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), > > +}; > > I might be misunderstanding this, but all of the 9 UART clocks below > share a common gate. AFAIK they don't share the common gate. Each uart comes with its own functional gate, bus gate and multiplexer. The diagram seems a little confusing: only one gate is drawn to avoid duplication. This applies for PWM as well. There're 10 different UART clock/reset registers, each contains a gate bit for functional clock and one for bus clock, confirming the idea[1]. > I *think* that gate should be represented as a distinct clock so that > it can be properly reference-counted. There are numerous cases of this. > Also note there is no uart1 clock; I think the clock tree diagram > erroneously calls the first clock "uart1" (not uart0). The clocks are reordered to match our current devicetree, where the first uart is uart0 and uart1 is assigned to the one in RCPU (real-time CPU) region. > > +static CCU_MUX_GATE_DEFINE(uart0_clk, "uart0_clk", uart_clk_parents, > > + APBC_UART1_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + CLK_IS_CRITICAL); > > +static CCU_MUX_GATE_DEFINE(uart2_clk, "uart2_clk", uart_clk_parents, > > + APBC_UART2_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart3_clk, "uart3_clk", uart_clk_parents, > > + APBC_UART3_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart4_clk, "uart4_clk", uart_clk_parents, > > + APBC_UART4_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart5_clk, "uart5_clk", uart_clk_parents, > > + APBC_UART5_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart6_clk, "uart6_clk", uart_clk_parents, > > + APBC_UART6_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart7_clk, "uart7_clk", uart_clk_parents, > > + APBC_UART7_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart8_clk, "uart8_clk", uart_clk_parents, > > + APBC_UART8_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(uart9_clk, "uart9_clk", uart_clk_parents, > > + APBC_UART9_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(gpio_clk, "gpio_clk", CCU_PARENT_NAME(vctcxo_24m), > > + APBC_GPIO_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 0); > > + > > +static const struct clk_parent_data pwm_parents[] = { > > + CCU_PARENT_HW(pll1_d192_12p8), > > + CCU_PARENT_NAME(osc), > > +}; > > The 20 PMIC clocks share a common gate, and this is another example > where I think that should be represented separate from all of these. > (I'm not going to point out any more such instances.) > > > +static CCU_MUX_GATE_DEFINE(pwm0_clk, "pwm0_clk", pwm_parents, > > + APBC_PWM0_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm1_clk, "pwm1_clk", pwm_parents, > > + APBC_PWM1_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm2_clk, "pwm2_clk", pwm_parents, > > + APBC_PWM2_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm3_clk, "pwm3_clk", pwm_parents, > > + APBC_PWM3_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm4_clk, "pwm4_clk", pwm_parents, > > + APBC_PWM4_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm5_clk, "pwm5_clk", pwm_parents, > > + APBC_PWM5_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm6_clk, "pwm6_clk", pwm_parents, > > + APBC_PWM6_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm7_clk, "pwm7_clk", pwm_parents, > > + APBC_PWM7_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm8_clk, "pwm8_clk", pwm_parents, > > + APBC_PWM8_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm9_clk, "pwm9_clk", pwm_parents, > > + APBC_PWM9_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm10_clk, "pwm10_clk", pwm_parents, > > + APBC_PWM10_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm11_clk, "pwm11_clk", pwm_parents, > > + APBC_PWM11_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm12_clk, "pwm12_clk", pwm_parents, > > + APBC_PWM12_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm13_clk, "pwm13_clk", pwm_parents, > > + APBC_PWM13_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm14_clk, "pwm14_clk", pwm_parents, > > + APBC_PWM14_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm15_clk, "pwm15_clk", pwm_parents, > > + APBC_PWM15_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm16_clk, "pwm16_clk", pwm_parents, > > + APBC_PWM16_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm17_clk, "pwm17_clk", pwm_parents, > > + APBC_PWM17_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm18_clk, "pwm18_clk", pwm_parents, > > + APBC_PWM18_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(pwm19_clk, "pwm19_clk", pwm_parents, > > + APBC_PWM19_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 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, "ssp3_clk", ssp_parents, > > + APBC_SSP3_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(rtc_clk, "rtc_clk", CCU_PARENT_NAME(osc), > > + APBC_RTC_CLK_RST, > > + 0x82, 0x82, 0x0, > > + 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, "twsi0_clk", twsi_parents, > > + APBC_TWSI0_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi1_clk, "twsi1_clk", twsi_parents, > > + APBC_TWSI1_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi2_clk, "twsi2_clk", twsi_parents, > > + APBC_TWSI2_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi4_clk, "twsi4_clk", twsi_parents, > > + APBC_TWSI4_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi5_clk, "twsi5_clk", twsi_parents, > > + APBC_TWSI5_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi6_clk, "twsi6_clk", twsi_parents, > > + APBC_TWSI6_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi7_clk, "twsi7_clk", twsi_parents, > > + APBC_TWSI7_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(twsi8_clk, "twsi8_clk", twsi_parents, > > + APBC_TWSI8_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 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, "timers1_clk", timer_parents, > > + APBC_TIMERS1_CLK_RST, > > + 4, 3, 0x3, 0x3, 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(timers2_clk, "timers2_clk", timer_parents, > > + APBC_TIMERS2_CLK_RST, > > + 4, 3, 0x3, 0x3, 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(aib_clk, "aib_clk", CCU_PARENT_NAME(vctcxo_24m), > > + APBC_AIB_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(onewire_clk, "onewire_clk", CCU_PARENT_NAME(vctcxo_24m), > > + APBC_ONEWIRE_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 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, "sspa0_clk", sspa_parents, > > + APBC_SSPA0_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_MUX_GATE_DEFINE(sspa1_clk, "sspa1_clk", sspa_parents, > > + APBC_SSPA1_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(dro_clk, "dro_clk", CCU_PARENT_HW(apb_clk), > > + APBC_DRO_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(ir_clk, "ir_clk", CCU_PARENT_HW(apb_clk), > > + APBC_IR_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(tsen_clk, "tsen_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TSEN_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(ipc_ap2aud_clk, "ipc_ap2aud_clk", CCU_PARENT_HW(apb_clk), > > + APBC_IPC_AP2AUD_CLK_RST, > > + BIT(1), BIT(1), 0x0, > > + 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, "can0_clk", can_parents, > > + APBC_CAN0_CLK_RST, > > + 4, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(can0_bus_clk, "can0_bus_clk", CCU_PARENT_NAME(vctcxo_24m), > > + APBC_CAN0_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > Here we begin a bunch of definitions of "bus clocks". They are all > simply gates, and the clock tree diagram shows these paired with > another gated parent. Please note in the diagram, the clock beside the bus clock isn't the parent of the bus clock. Without special notes, all bus clocks in the APBC region take apb_clk as parent. All bus clocks in the APMU region take pmua_aclk as parent. > This says to me that in order to use a "child clock" in this > situation, both the "regular gate" and the "bus gate" clock must be > defined, and a reference to it taken (with clk_get() or similar). No, functional clock and bus clock are completely irrelevant. They control different parts of a peripheral. Taking UART as example, you do need to take both function and bus clock for normal operation; but if the bus clock is disabled, the UART could continue to send data. Only the bus component won't work and the registers couldn't be accessed. > Can you confirm this? > > + > > +static CCU_GATE_DEFINE(uart0_bus_clk, "uart0_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART1_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + CLK_IS_CRITICAL); > > +static CCU_GATE_DEFINE(uart2_bus_clk, "uart2_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART2_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart3_bus_clk, "uart3_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART3_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart4_bus_clk, "uart4_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART4_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart5_bus_clk, "uart5_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART5_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart6_bus_clk, "uart6_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART6_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart7_bus_clk, "uart7_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART7_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart8_bus_clk, "uart8_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART8_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(uart9_bus_clk, "uart9_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_UART9_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(gpio_bus_clk, "gpio_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_GPIO_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(pwm0_bus_clk, "pwm0_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM0_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm1_bus_clk, "pwm1_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM1_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm2_bus_clk, "pwm2_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM2_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm3_bus_clk, "pwm3_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM3_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm4_bus_clk, "pwm4_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM4_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm5_bus_clk, "pwm5_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM5_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm6_bus_clk, "pwm6_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM6_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm7_bus_clk, "pwm7_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM7_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm8_bus_clk, "pwm8_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM8_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm9_bus_clk, "pwm9_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM9_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm10_bus_clk, "pwm10_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM10_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm11_bus_clk, "pwm11_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM11_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm12_bus_clk, "pwm12_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM12_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm13_bus_clk, "pwm13_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM13_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm14_bus_clk, "pwm14_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM14_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm15_bus_clk, "pwm15_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM15_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm16_bus_clk, "pwm16_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM16_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm17_bus_clk, "pwm17_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM17_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm18_bus_clk, "pwm18_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM18_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pwm19_bus_clk, "pwm19_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_PWM19_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(ssp3_bus_clk, "ssp3_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_SSP3_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(rtc_bus_clk, "rtc_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_RTC_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(twsi0_bus_clk, "twsi0_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI0_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi1_bus_clk, "twsi1_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI1_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi2_bus_clk, "twsi2_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI2_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi4_bus_clk, "twsi4_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI4_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi5_bus_clk, "twsi5_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI5_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi6_bus_clk, "twsi6_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI6_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi7_bus_clk, "twsi7_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI7_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(twsi8_bus_clk, "twsi8_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TWSI8_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(timers1_bus_clk, "timers1_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TIMERS1_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(timers2_bus_clk, "timers2_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TIMERS2_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(aib_bus_clk, "aib_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_AIB_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(onewire_bus_clk, "onewire_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_ONEWIRE_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(sspa0_bus_clk, "sspa0_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_SSPA0_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(sspa1_bus_clk, "sspa1_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_SSPA1_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(tsen_bus_clk, "tsen_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_TSEN_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(ipc_ap2aud_bus_clk, "ipc_ap2aud_bus_clk", CCU_PARENT_HW(apb_clk), > > + APBC_IPC_AP2AUD_CLK_RST, > > + BIT(0), BIT(0), 0x0, > > + 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), > > +}; > > OK I'm just going to point out that this is the first clock that > defines a "FC" (frequency change) register/bit. It must be set > by software when the clock control register is updated, and > the hardware will clear that bit to signal that the change > is complete. > > > +static CCU_DIV_FC_MUX_DEFINE(pmua_aclk, "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", 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", 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_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, "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, "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", 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), > > +}; > > The following clock is named "cpu_c1_pclk" on the clock tree diagram. It's renamed to match the clock for cluster 0. > > +static CCU_MUX_FC_DEFINE(cpu_c1_core_clk, "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, "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_clk", jpg_parents, > > + APMU_JPG_CLK_RES_CTRL, > > + 5, 3, BIT(15), > > + 2, 3, BIT(1), BIT(1), 0x0, > > + 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_clk", ccic2phy_parents, > > + APMU_CSI_CCIC2_CLK_RES_CTRL, > > + 7, 1, BIT(5), BIT(5), 0x0, > > + 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_clk", ccic3phy_parents, > > + APMU_CSI_CCIC2_CLK_RES_CTRL, > > + 31, 1, BIT(30), BIT(30), 0x0, > > + 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_clk", csi_parents, > > + APMU_CSI_CCIC2_CLK_RES_CTRL, > > + 20, 3, BIT(15), > > + 16, 3, BIT(4), BIT(4), 0x0, > > + 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, "camm0_clk", camm_parents, > > + APMU_CSI_CCIC2_CLK_RES_CTRL, > > + 23, 4, 8, 2, > > + BIT(28), BIT(28), 0x0, > > + 0); > > +static CCU_DIV_MUX_GATE_DEFINE(camm1_clk, "camm1_clk", camm_parents, > > + APMU_CSI_CCIC2_CLK_RES_CTRL, > > + 23, 4, 8, 2, BIT(6), BIT(6), 0x0, > > + 0); > > +static CCU_DIV_MUX_GATE_DEFINE(camm2_clk, "camm2_clk", camm_parents, > > + APMU_CSI_CCIC2_CLK_RES_CTRL, > > + 23, 4, 8, 2, BIT(3), BIT(3), 0x0, > > + 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_clk", isp_cpp_parents, > > + APMU_ISP_CLK_RES_CTRL, > > + 24, 2, 26, 1, BIT(28), BIT(28), 0x0, > > + 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_clk", isp_bus_parents, > > + APMU_ISP_CLK_RES_CTRL, > > + 18, 3, BIT(23), > > + 21, 2, BIT(17), BIT(17), 0x0, > > + 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_clk", isp_parents, > > + APMU_ISP_CLK_RES_CTRL, > > + 4, 3, BIT(7), > > + 8, 2, BIT(1), BIT(1), 0x0, > > + 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_DIV2_FC_MUX_GATE_DEFINE(dpu_mclk, "dpu_mclk", dpumclk_parents, > > + APMU_LCD_CLK_RES_CTRL2, > > + APMU_LCD_CLK_RES_CTRL1, > > + 1, 4, BIT(29), > > + 5, 3, BIT(0), BIT(0), 0x0, > > + 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, "dpu_esc_clk", dpuesc_parents, > > + APMU_LCD_CLK_RES_CTRL1, > > + 0, 2, BIT(2), BIT(2), 0x0, > > + 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), > > The next two parent clocks are duplicates. It looks this way on the > clock tree diagram as well. Is this correct? Can you find out from > SpacemiT whether one of them is actually a different clock (like > pll2_d6 or something)? It makes no sense to have two multiplexed > parent clocks with the same source. Yes, will confirm it later. The register description[2] suggests it's wrong (there aren't two configuration for MIPI_BIT_CLK_SEL resulting in the same frequency). > > + CCU_PARENT_HW(pll2_d8), > > + CCU_PARENT_HW(pll2_d8), > > +}; > > +static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, "dpu_bit_clk", dpubit_parents, > > + APMU_LCD_CLK_RES_CTRL1, > > + 17, 3, BIT(31), > > + 20, 3, BIT(16), BIT(16), 0x0, > > + 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_DIV2_FC_MUX_GATE_DEFINE(dpu_pxclk, "dpu_pxclk", dpupx_parents, > > + APMU_LCD_CLK_RES_CTRL2, > > + APMU_LCD_CLK_RES_CTRL1, > > + 17, 4, BIT(30), > > + 21, 3, BIT(16), BIT(16), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(dpu_hclk, "dpu_hclk", CCU_PARENT_HW(pmua_aclk), > > + APMU_LCD_CLK_RES_CTRL1, > > + BIT(5), BIT(5), 0x0, > > + 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_clk", dpu_spi_parents, > > + APMU_LCD_SPI_CLK_RES_CTRL, > > + 8, 3, BIT(7), > > + 12, 3, BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(dpu_spi_hbus_clk, "dpu_spi_hbus_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_LCD_SPI_CLK_RES_CTRL, > > + BIT(3), BIT(3), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(dpu_spi_bus_clk, "dpu_spi_bus_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_LCD_SPI_CLK_RES_CTRL, > > + BIT(5), BIT(5), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(dpu_spi_aclk, "dpu_spi_aclk", CCU_PARENT_HW(pmua_aclk), > > + APMU_LCD_SPI_CLK_RES_CTRL, > > + BIT(6), BIT(6), 0x0, > > + 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_clk", v2d_parents, > > + APMU_LCD_CLK_RES_CTRL1, > > + 9, 3, BIT(28), > > + 12, 2, BIT(8), BIT(8), 0x0, > > + 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_clk", ccic_4x_parents, > > + APMU_CCIC_CLK_RES_CTRL, > > + 18, 3, BIT(15), > > + 23, 2, BIT(4), BIT(4), 0x0, > > + 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_clk", ccic1phy_parents, > > + APMU_CCIC_CLK_RES_CTRL, > > + 7, 1, BIT(5), BIT(5), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(sdh_axi_aclk, "sdh_axi_aclk", CCU_PARENT_HW(pmua_aclk), > > + APMU_SDH0_CLK_RES_CTRL, > > + BIT(3), BIT(3), 0x0, > > + 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, "sdh0_clk", sdh01_parents, > > + APMU_SDH0_CLK_RES_CTRL, > > + 8, 3, BIT(11), > > + 5, 3, BIT(4), BIT(4), 0x0, > > + 0); > > +static CCU_DIV_FC_MUX_GATE_DEFINE(sdh1_clk, "sdh1_clk", sdh01_parents, > > + APMU_SDH1_CLK_RES_CTRL, > > + 8, 3, BIT(11), > > + 5, 3, BIT(4), BIT(4), 0x0, > > + 0); > > This is strange too. sdh2_parents is identical to sdh01_parents. No, it isn't. pll2_d5 is a parent for sdh0/1; for sdh2, pll1_d3_819p2 replaces it. > All of the bits used are identical for the three control registers. > The clock tree diagram shows all three of these sdhx clocks sharing > a single parent mux. Why is sdh2_parents defined separately? I don't think the mux is shared. It should be the same case for UART/PWM clocks, as described earlier. > > +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_clk", sdh2_parents, > > + APMU_SDH2_CLK_RES_CTRL, > > + 8, 3, BIT(11), > > + 5, 3, BIT(4), BIT(4), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(usb_axi_clk, "usb_axi_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_USB_CLK_RES_CTRL, > > + BIT(1), BIT(1), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(usb_p1_aclk, "usb_p1_aclk", CCU_PARENT_HW(pmua_aclk), > > + APMU_USB_CLK_RES_CTRL, > > + BIT(5), BIT(5), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(usb30_clk, "usb30_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_USB_CLK_RES_CTRL, > > + BIT(8), BIT(8), 0x0, > > + 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_clk", qspi_parents, > > + APMU_QSPI_CLK_RES_CTRL, > > + 9, 3, BIT(12), > > + 6, 3, BIT(4), BIT(4), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(qspi_bus_clk, "qspi_bus_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_QSPI_CLK_RES_CTRL, > > + BIT(3), BIT(3), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(dma_clk, "dma_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_DMA_CLK_RES_CTRL, > > + BIT(3), BIT(3), 0x0, > > + 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_clk", aes_parents, > > + APMU_AES_CLK_RES_CTRL, > > + 6, 1, BIT(5), BIT(5), 0x0, > > + 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_clk", vpu_parents, > > + APMU_VPU_CLK_RES_CTRL, > > + 13, 3, BIT(21), > > + 10, 3, > > + BIT(3), BIT(3), 0x0, > > + 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_clk", gpu_parents, > > + APMU_GPU_CLK_RES_CTRL, > > + 12, 3, BIT(15), > > + 18, 3, > > + BIT(4), BIT(4), 0x0, > > + 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_clk", emmc_parents, > > + APMU_PMUA_EM_CLK_RES_CTRL, > > + 8, 3, BIT(11), > > + 6, 2, > > + BIT(4), BIT(4), 0x0, > > + 0); > > +static CCU_DIV_GATE_DEFINE(emmc_x_clk, "emmc_x_clk", CCU_PARENT_HW(pll1_d2_1228p8), > > + APMU_PMUA_EM_CLK_RES_CTRL, > > + 12, 3, BIT(15), BIT(15), 0x0, > > + 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_clk", audio_parents, > > + APMU_AUDIO_CLK_RES_CTRL, > > + 4, 3, BIT(15), > > + 7, 3, > > + BIT(12), BIT(12), 0x0, > > + 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_mclk", hdmi_parents, > > + APMU_HDMI_CLK_RES_CTRL, > > + 1, 4, BIT(29), > > + 5, 3, > > + BIT(0), BIT(0), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(pcie0_clk, "pcie0_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_PCIE_CLK_RES_CTRL_0, > > + 0x7, 0x7, 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pcie1_clk, "pcie1_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_PCIE_CLK_RES_CTRL_1, > > + 0x7, 0x7, 0x0, > > + 0); > > +static CCU_GATE_DEFINE(pcie2_clk, "pcie2_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_PCIE_CLK_RES_CTRL_2, > > + 0x7, 0x7, 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(emac0_bus_clk, "emac0_bus_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_EMAC0_CLK_RES_CTRL, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(emac0_ptp_clk, "emac0_ptp_clk", CCU_PARENT_HW(pll2_d6), > > + APMU_EMAC0_CLK_RES_CTRL, > > + BIT(15), BIT(15), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(emac1_bus_clk, "emac1_bus_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_EMAC1_CLK_RES_CTRL, > > + BIT(0), BIT(0), 0x0, > > + 0); > > +static CCU_GATE_DEFINE(emac1_ptp_clk, "emac1_ptp_clk", CCU_PARENT_HW(pll2_d6), > > + APMU_EMAC1_CLK_RES_CTRL, > > + BIT(15), BIT(15), 0x0, > > + 0); > > + > > +static CCU_GATE_DEFINE(emmc_bus_clk, "emmc_bus_clk", CCU_PARENT_HW(pmua_aclk), > > + APMU_PMUA_EM_CLK_RES_CTRL, > > + BIT(3), BIT(3), 0, > > + 0); > > +/* APMU clocks end */ > > + > > You could reduce some duplication here using a simple > macro for defining the address of the clk_hw structure: > > #define CLK_HW(x) &x.common.hw > > (Add parentheses if you like.) It saves 3 characters for > each clock... Looks good to me, but > > +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { > > + .hws = { > > + [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, > > + > > + }, > > + .num = CLK_APBS_NUM, > > It sure would be nice to be able to use ARRAY_SIZE() here but > alas I don't think it's possible here. CLK_*_NUM will be removed in the next revision, as it's actually not a binding. I'm looking for a better way to represent these clocks. Maybe represent the clocks with (ID, struct clk_hw *) pairs and generate struct clk_hw_onecell_data dynamically during probe. > > +}; > > + > > +static struct clk_hw_onecell_data k1_ccu_mpmu_clks = { > > + .hws = { > > + [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_351] = &pll1_d7_351p08.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, > > + }, > > + .num = CLK_MPMU_NUM, > > +}; > > + > > +static struct clk_hw_onecell_data k1_ccu_apbc_clks = { > > + .hws = { > > + [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, > > + }, > > + .num = CLK_APBC_NUM, > > +}; > > + > > +static struct clk_hw_onecell_data k1_ccu_apmu_clks = { > > + .hws = { > > + [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] = &pcie0_clk.common.hw, > > + [CLK_PCIE1] = &pcie1_clk.common.hw, > > + [CLK_PCIE2] = &pcie2_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, > > + }, > > + .num = CLK_APMU_NUM > > +}; > > + > > The only time the "PLL lock" is needed is with PLL clocks. Can you > encode this flag, or perhaps the address of the lock register, > into the ccu_pll structure instead somehow? > > If so you wouldn't need this spacemit_ccu_data structure, and could > simply use the clk_hw_onecell_data structure in its place. We could scan the clk_hw_onecell_data array before the probing, but I don't think it's worth. As the raw clk_hw_onecell_data is likely to be removed in the next revision, I'll try to avoid the flag during refactoring. > > +struct spacemit_ccu_data { > > + struct clk_hw_onecell_data *hw_clks; > > + bool need_pll_lock; > > +}; > > + > > You should get rid of the spacemit_ccu_priv structure. More below. > > > +struct spacemit_ccu_priv { > > + const struct spacemit_ccu_data *data; > > + struct regmap *base; > > + struct regmap *lock_base; > > +}; > > + > > You use the fields in the above priv structure temporarily, > to pass three values into spacemit_ccu_register(). Just pass > those three values as arguments to this function, and get rid > of the structure. Thanks, it makes sense. > > +static int spacemit_ccu_register(struct device *dev, > > + struct spacemit_ccu_priv *priv) > > +{ > > + const struct spacemit_ccu_data *data = priv->data; > > You never use the data pointer, except when you're using > its hw_clks field. So it would be better to do: > > struct clk_hw_onecell_data *hw_clks = &priv->data.hw_clks; > > Then use that. If you eliminated the spacemit_ccu_data > type as I suggested above, you could just pass in the > clk_hw_onecell_data pointer as an argument. > > > + int i, ret; > > + > > + for (i = 0; i < data->hw_clks->num; i++) { > > + struct clk_hw *hw = data->hw_clks->hws[i]; > > I think the hw pointer should probably be const. There may > be other spots that should be const as well. Yes, I don't think a lot about the constant when writing this, will look through later and see where it applies. > > + struct ccu_common *common; > > + const char *name; > > Don't bother with the name variable. > > > + > > + if (!hw) > > + continue; > > + > > + common = hw_to_ccu_common(hw); > > + name = hw->init->name; > > + > > The next two pointers are regmap pointers. Could the > name of the fields suggest that? (They sound like raw > memory addresses to me.) Do "common.regmap" and "common.lock_regmap" sound better for you? > > + common->base = priv->base; > > + common->lock_base = priv->lock_base; > > + > > + ret = devm_clk_hw_register(dev, hw); > > + if (ret) { > > + dev_err(dev, "Cannot register clock %d - %s\n", > > + i, name); > > Just use hw->init->name here instead of name. Will change in the next version. > > + return ret; > > + } > > + } > > + > > + return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, > > + data->hw_clks); > > +} > > + > > +static int k1_ccu_probe(struct platform_device *pdev) > > +{ > > + const struct spacemit_ccu_data *data; > > + struct regmap *base_map, *lock_map = NULL; > > + struct device *dev = &pdev->dev; > > + struct spacemit_ccu_priv *priv; > > + struct device_node *parent; > > + int ret; > > + > > + data = of_device_get_match_data(dev); > > You can tell by inspection that data will never be null. > So the next two lines can go away. Will, will fix this in the next version. > > + if (WARN_ON(!data)) > > + return -EINVAL; > > + > > + parent = of_get_parent(dev->of_node); > > + base_map = syscon_node_to_regmap(parent); > > + of_node_put(parent); > > + > > + if (IS_ERR(base_map)) > > + return dev_err_probe(dev, PTR_ERR(base_map), > > + "failed to get regmap\n"); > > + > > + if (data->need_pll_lock) { > > This is the weird part that requires the MPMU CCU to get > fully probed before the APBC CCU can complete its probe. > It would be nice if all the clocks shared their address > ranges somehow to avoid this, but I'm not sure that maps > well to the hardware. > > > + lock_map = syscon_regmap_lookup_by_phandle(dev->of_node, > > + "spacemit,mpmu"); > > + if (IS_ERR(lock_map)) > > + return dev_err_probe(dev, PTR_ERR(lock_map), > > + "failed to get lock regmap\n"); > > + } > > + > > You allocate the priv structure, but never free it (a bug). > It only holds 3 values; just pass them as arguments when > registering the CCU, as suggested above. The allocation is done through a devres variant, it should be released automatically. Please correct me if I'm wrong. > > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > > + if (!priv) > > + return -ENOMEM; > > + > > + priv->data = data; > > + priv->base = base_map; > > + priv->lock_base = lock_map; > > + > > + ret = spacemit_ccu_register(dev, priv); > > + if (ret) > > + return dev_err_probe(dev, ret, "failed to register clocks\n"); > > + > > + return 0; > > +} > > + > > +static const struct spacemit_ccu_data k1_ccu_apbs_data = { > > + .need_pll_lock = true, > > + .hw_clks = &k1_ccu_apbs_clks, > > +}; > > + > > +static const struct spacemit_ccu_data k1_ccu_mpmu_data = { > > + .need_pll_lock = false, > > + .hw_clks = &k1_ccu_mpmu_clks, > > +}; > > + > > +static const struct spacemit_ccu_data k1_ccu_apbc_data = { > > + .need_pll_lock = false, > > + .hw_clks = &k1_ccu_apbc_clks, > > +}; > > + > > +static const struct spacemit_ccu_data k1_ccu_apmu_data = { > > + .need_pll_lock = false, > > + .hw_clks = &k1_ccu_apmu_clks, > > +}; > > + > > +static const struct of_device_id of_k1_ccu_match[] = { > > + { > > + .compatible = "spacemit,k1-ccu-apbs", > > + .data = &k1_ccu_apbs_data, > > + }, > > + { > > + .compatible = "spacemit,k1-ccu-mpmu", > > + .data = &k1_ccu_mpmu_data, > > + }, > > + { > > + .compatible = "spacemit,k1-ccu-apbc", > > + .data = &k1_ccu_apbc_data, > > + }, > > + { > > + .compatible = "spacemit,k1-ccu-apmu", > > + .data = &k1_ccu_apmu_data, > > + }, > > + { } > > +}; > > +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"); > > End of "ccu-k1.c"!!! It's really a lot of lines lol > > diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h > > new file mode 100644 > > index 000000000000..242461ee592f > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_common.h > > @@ -0,0 +1,51 @@ > > +/* 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 *base; > > + struct regmap *lock_base; > > + > > + union { > > The following struct is used for all clocks except PLL. > > > + struct { > > + u32 reg_ctrl; > > The reg_sel field is never needed and can go away. > > > + u32 reg_sel; > > + u32 reg_fc; > > + u32 fc; > > + }; > > Add a comment indicating that the following struct is used only > for PLL type clocks. > > > + struct { > > + u32 reg_swcr1; > > The reg_swcr2 field is never needed and can go away. > > > + u32 reg_swcr2; > > + u32 reg_swcr3; > > + }; > > + }; > > + > > + unsigned long flags; > > I don't believe the name field here is *ever* used, despite > being assigned for every clock. The name is put into the > hw.init structure, so there's no need for this one. > > > + const char *name; > > The parent_names field here is also not used. The names of > the parent clocks are provided to the hw.init structure > though. > > > + const char * const *parent_names; > > The num_parents field isn't necessary. It seems to be used when > looping through parents in ccu_mix_calc_best_rate(), but I think > the number can be looked up with clk_hw_get_num_parents() as is > done in ccu_mux_get_parent(). Thanks, comments above all make sense to me. Will adapt them in the next revision. > > + int num_parents; > > + > > + 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); > > +} > > + > > Although I think the following macros are nice and concise, they > obscure the fact that they're essentially renaming existing > regmap operations. They don't really add much value; the > regmap functions are pretty clear. They're just for saving some characters, regmap_read(common->base, common->reg_ctrl, &tmp) is much longer than ccu_read(common, ctrl, &tmp) Of course it could be changed, if you're still strongly against it, > Although regmap_read() returns a value, you never check for an > error when calling ccu_read(). I don't expect that access to a MMIO regmap could fail. There shouldn't be any cases of failure, so I don't check it. > > +#define ccu_read(reg, c, val) regmap_read((c)->base, (c)->reg_##reg, val) > > The ccu_write() macro is never used. Yes, most operations are done through ccu_update, which ensures the atomicity. As reset controllers that will be introduced in the future share some registers with the clock controller, it's a must. I agree on a removal. > > +#define ccu_write(reg, c, val) regmap_write((c)->base, (c)->reg_##reg, val) > > Although regmap_update_bits() returns a value, you never check > for an error when calling ccu_update(). > > > +#define ccu_update(reg, c, mask, val) \ > > + regmap_update_bits((c)->base, (c)->reg_##reg, mask, val) > > > You *do* look at the return value for ccu_poll()!!! Because polling for a condition MAY timeout and we want to report the failure if the PLL doesn't lock or the frequency changing request does not complete in a reasonable period. This differs from the simple read and update cases. > > +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \ > > + regmap_read_poll_timeout_atomic((c)->base, (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..1df555888ecb > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_ddn.c > > @@ -0,0 +1,140 @@ > > +// 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 "ccu_ddn.h" > > What does "DDN" stand for? I'm not sure, the name is kept from the vendor driver. I could change it to a more descriptive name, like "fraction-factor". Thanks for pointing out problems in frequency calculation, they all make sense. I have to admit this clock type is quite ugly and plan to rewrite it to reuse code in clk-fraction-divider in the next version. > > + > > +/* > > + * It is M/N clock > > + * > > + * Fout from synthesizer can be given from two equations: > > + * numerator/denominator = Fin / (Fout * factor) > > + */ > > The next three functions are never used and can be removed. > > The reason is that the only time they'd be used is when > CCU_DDN_GATE_DEFINE() (defined in "ccu_ddn.h") is called, > but it never is. So you can get rid of CCU_DDN_GATE_DEFINE(), > and once you do that there's no need for ccu_ddn_disable(), > ccu_ddn_enable(), and ccu_ddn_is_enabled(). Furthermore, > these three functions are the only place the "reg_sel" > field in the ccu_common structure is used, and without > these functions, that field serves no purpose. So that > field can go away, and there's no need to provide a value > for initializing it in all those clock definition macros. When cleaning up the driver from the vendor source, I tried to keep most clock types included since future SpacemiT SoCs made may make use of them. It seems too farsighted as this patch is already quite large. I'll remove the unused clock types as well as the reg_sel field, thanks. > > +static void ccu_ddn_disable(struct clk_hw *hw) > > +{ > > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > > + struct ccu_common *common = &ddn->common; > > + > > + ccu_update(sel, common, ddn->gate, 0); > > +} > > + > > +static int ccu_ddn_enable(struct clk_hw *hw) > > +{ > > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > > + struct ccu_common *common = &ddn->common; > > + > > + ccu_update(sel, common, ddn->gate, ddn->gate); > > + > > + return 0; > > +} > > + > > +static int ccu_ddn_is_enabled(struct clk_hw *hw) > > +{ > > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > > + struct ccu_common *common = &ddn->common; > > + u32 tmp; > > + > > + ccu_read(sel, common, &tmp); > > + > > + return tmp & ddn->gate; > > +} > > + > > Defining a ->determine_rate() is better than ->round_rate(), > given you implement multiplexed parent clocks. > > > +static long clk_ddn_round_rate(struct clk_hw *hw, unsigned long drate, > > + unsigned long *prate) > > +{ > > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > > + struct ccu_ddn_config *params = &ddn->ddn; > > + unsigned long rate = 0, prev_rate; > > + unsigned long result; > > + int i; > > + > > + for (i = 0; i < params->tbl_size; i++) { > > + prev_rate = rate; > > I'm going to say more about this below, but I think the > factor can be removed and hard-coded here. Alright, we could hardcode it for now to make the patch as simple as possible. If different factors are used in future SoCs, it isn't hard to add it back later. > > + rate = (*prate * params->tbl[i].den) / > > + (params->tbl[i].num * params->info->factor); > > I think you want this to use >= in the comparison. If the > rate is *exactly* the desired rate, that's what you want > to use. > > > + if (rate > drate) > > + break; > > + } > > + > > I'm pretty sure the table size is always non-zero. And if > so, the above loop can be a do...while, which I think > would mean there's no need to to treat i == 0 specially. > You still would want to return 0 if no entry is found. > > > + if ((i == 0) || (i == params->tbl_size)) { > > + result = rate; > > + } else { > > + if ((drate - prev_rate) > (rate - drate)) > > + result = rate; > > + else > > + result = prev_rate; > > + } > > + > > + return result; > > +} > > + > > +static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, > > + unsigned long parent_rate) > > +{ > > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > > + struct ccu_ddn_config *params = &ddn->ddn; > > + unsigned int val, num, den; > > + unsigned long rate; > > + > > + ccu_read(ctrl, &ddn->common, &val); > > + > > + num = (val >> params->info->num_shift) & params->info->num_mask; > > + den = (val >> params->info->den_shift) & params->info->den_mask; > > + > > + if (!den) > > + return 0; > > + > > Here too, I think the factor can be hard-coded. > > > + rate = (parent_rate * den) / (num * params->info->factor); > > + > > + return rate; > > +} > > + > > +/* Configures new clock rate*/ > > +static int clk_ddn_set_rate(struct clk_hw *hw, unsigned long drate, > > + unsigned long prate) > > +{ > > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > > + struct ccu_ddn_config *params = &ddn->ddn; > > + struct ccu_ddn_info *info = params->info; > > + unsigned long rate = 0; > > + int i; > > + > > + for (i = 0; i < params->tbl_size; i++) { > > The way you calculate the output rate here tells me that your > "num" and "den" field names are completely backward. > > The numerator is the top part of a fraction and the > denominator is the bottom. I'm not a native speaker and haven't noted the error, thanks for pointing it out. Will fix it. > output_rate = parent_rate * numerator / (denominator * factor) > > Please fix this naming (carefully). (And I propose hard-coding > the factor here as well.) > > > + rate = (prate * params->tbl[i].den) / > > + (params->tbl[i].num * info->factor); > > + > > Here too, I think you want >=, not > in the comparison. > > > + if (rate > drate) > > + break; > > + } > > + > > + if (i > 0) > > + i--; > > + > > + ccu_update(ctrl, &ddn->common, > > + info->num_mask | info->den_mask, > > + (params->tbl[i].num << info->num_shift) | > > + (params->tbl[i].den << info->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, > > +}; > > + > > +const struct clk_ops spacemit_ccu_ddn_gate_ops = { > > + .disable = ccu_ddn_disable, > > + .enable = ccu_ddn_enable, > > + .is_enabled = ccu_ddn_is_enabled, > > + .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..6fa658bcd50a > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_ddn.h > > @@ -0,0 +1,84 @@ > > +/* 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" > > + > > This struct definition is fine, but I think the way you used the > fields is completely wrong (as I said above). > > > +struct ccu_ddn_tbl { > > + unsigned int num; > > + unsigned int den; > > +}; > > + > > Maybe the factor is included here to somehow match the way the > hardware works. In practice, the factor is an additional divider > for the parent clock rate. > > The reality is there are only two DDN clocks, and both of them > supply 2 for the factor. So rather than using a separate field > to hold that value, just incorporate the additional factor of 2 > where the rates are calculated in the code. Add a comment to > explain if it helps. > > > +struct ccu_ddn_info { > > + unsigned int factor; > > Here is a general comment that applies to a bunch of places in > this code. You can concisely define a field with a single > mask value. That means there is no need to define both a mask > (or a field width) and a shift. What you need is defined in > <linux/bitfield.h>. There you will find a set of macros > like FIELD_GET() and FIELD_PREP(), or alternatively a set > of inline functions like u32_get_bits() and u32_encode_bits(). I think both macros and inline functions in bitfield.h work with only constant masks. With non-constant masks, some checks cannot be optimized out, resulting in compilation errors. > > + unsigned int num_mask; > > + unsigned int den_mask; > > + unsigned int num_shift; > > + unsigned int den_shift; > > +}; > > So instead of defining this: > > static struct ccu_ddn_info uart_ddn_mask_info = { > .factor = 2, > .num_mask = 0x1fff, > .den_mask = 0x1fff, > .num_shift = 16, > .den_shift = 0, > }; > > You can define: > > static struct ccu_ddn_info uart_ddn_mask_info = { > .factor = 2, > .num_mask = 0x1fff0000, > .den_mask = 0x00001fff, > }; > > And then you use those masks with the helper functions > to extract or encode values from or two fields. I'm not > going to say more here about how to do that, but I'll > point out some more places where this suggestion applies. > > > + > > +struct ccu_ddn_config { > > + struct ccu_ddn_info *info; > > + struct ccu_ddn_tbl *tbl; > > + u32 tbl_size; > > I mentioned this earlier. In the two DDN clocks defined, both > have a single entry table. So rather than defining this with > a poitner to a table and a table size, just define the num > and den fields directly in this structure. > > > +}; > > + > > +struct ccu_ddn { > > + struct ccu_ddn_config ddn; > > + struct ccu_common common; > > + u32 gate; > > +}; > > + > > +#define CCU_DDN_CONFIG(_info, _table) \ > > + { \ > > + .info = (struct ccu_ddn_info *)_info, \ > > + .tbl = (struct ccu_ddn_tbl *)&_table, \ > > + .tbl_size = ARRAY_SIZE(_table), \ > > + } > > + > > +#define CCU_DDN_INIT(_name, _parent, _ops, _flags) \ > > + CLK_HW_INIT_HW(_name, &_parent.common.hw, &_ops, _flags) > > + > > +#define CCU_DDN_DEFINE(_struct, _name, _parent, _info, _table, \ > > + _reg_ctrl, _flags) \ > > + struct ccu_ddn _struct = { \ > > + .ddn = CCU_DDN_CONFIG(_info, _table), \ > > + .common = { \ > > + .reg_ctrl = _reg_ctrl, \ > > + .hw.init = CCU_DDN_INIT(_name, _parent, \ > > + spacemit_ccu_ddn_ops, \ > > + _flags), \ > > + } \ > > + } > > + > > As mentioned before, the following macro is never used and can be > removed. That means a bunch of other things can be removed as well. > > > +#define CCU_DDN_GATE_DEFINE(_struct, _name, _parent, _info, _table, \ > > + _reg_ddn, _reg_gate, _gate_mask, _flags) \ > > + struct ccu_ddn _struct = { \ > > + .ddn = CCU_DDN_CONFIG(_info, _table), \ > > + .common = { \ > > + .reg_ctrl = _reg_ddn, \ > > + .reg_sel = _reg_gate, \ > > + .hw.init = CCU_DDN_INIT(_name, _parent, \ > > + &spacemit_ccu_ddn_gate_ops, \ > > + _flags), \ > > + } \ > > + .gate = _gate_mask, \ > > + } > > + > > +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, spacemit_ccu_ddn_gate_ops; > > + > > +#endif > > diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c > > new file mode 100644 > > index 000000000000..b46eeb59faea > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_mix.c > > @@ -0,0 +1,304 @@ > > +// 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; > > + struct ccu_gate_config *gate = mix->gate; > > + > > I will explain more below. But gate->val_disable can go > away and you should just use 0 here. > > > + ccu_update(ctrl, common, gate->gate_mask, gate->val_disable); > > +} > > + > > +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; > > + u32 val_enable, mask; > > + u32 tmp; > > + > > + val_enable = gate->val_enable; > > + mask = gate->gate_mask; > > + > > + ccu_update(ctrl, common, mask, val_enable); > > + > > + return ccu_poll(ctrl, common, tmp, (tmp & mask) == val_enable, > > + 10, MIX_TIMEOUT); > > Here too, val_enable can just go away, and this can be > simplified: > > return ccu_poll(ctrl, common, tmp, tmp & mask, 10, MIX_TIMEOUT); > > > +} > > + > > +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; > > + struct ccu_gate_config *gate = mix->gate; > > + u32 tmp; > > + > > + ccu_read(ctrl, common, &tmp); > > + > > With val_enable gone, this would be: > > return !!(tmp & gate->gate_mask); > > > + return (tmp & gate->gate_mask) == gate->val_enable; > > +} > > + > > +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, ®); > > + > > + val = reg >> div->shift; > > + val &= (1 << div->width) - 1; > > + > > + val = divider_recalc_rate(hw, parent_rate, val, div->table, > > + div->flags, 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); > > + struct ccu_common *common = &mix->common; > > + struct ccu_div_config *div = mix->div; > > + u32 div_max = div ? 1 << div->width : 1; > > + unsigned long best_rate = 0; > > + > > + for (int i = 0; i < common->num_parents; i++) { > > + struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i); > > unsigned long parent_rate; > > > + > > + if (!parent) > > + continue; > > + > > + unsigned long parent_rate = clk_hw_get_rate(parent); > > parent_rate = clk_hw_get_rate(parent); Thanks, will fix the style. > > + > > + 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; > > With my suggested change for using bitfields, this would be: > > current_div = u32_get_bits(tmp, div->mask); > Sadly, I don't think it compiles :( > > + > > + if (current_div == target_div) > > + return 0; > > + > > + tmp = GENMASK(div->width + div->shift - 1, div->shift); > > And the above would not be required; just use div->mask (which > I'll define a little later). > > > + > > + 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, ®); > > + > > + parent = reg >> mux->shift; > > + parent &= (1 << mux->width) - 1; > > + > > + if (mux->table) { > > + int num_parents = clk_hw_get_num_parents(&common->hw); > > + int i; > > + > > + for (i = 0; i < num_parents; i++) > > + if (mux->table[i] == parent) > > + return i; > > I think if there is no table entry found there should at > least be a WARN() call or something. Yes, it makes sense. > > + } > > + > > + 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; > > + > > + if (mux->table) > > You should verify that the index is less than the > number of entries in the table before you blindly > dereference it here. The desired index is found by searching in clk_fetch_parent_index(), thus is always be valid, isn't it? However, this is a dead codepath: no clock hardware comes with a valid table. The whole if could be removed for simplication. > > + index = mux->table[index]; > > + > > + 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..97a3a099b42f > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_mix.h > > @@ -0,0 +1,309 @@ > > +/* 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 gate_mask; > > Get rid of the val_enable and val_disable definitions. In all cases, > val_enable is equal to gate_mask (and in fact, there are a few gate > masks made of more than a single bit). And in all cases, val_disable > is zero. So just use gate_mask and 0 to represent enable and disable. Will take it, this will simplify definitions a lot. > > + u32 val_enable; > > + u32 val_disable; > > + u32 flags; > > When you have a flags field like this, it's helpful to add a comment > that indicates what values it can take on. I believe in all cases > like this, the flags values are standard clock framework flags. Yes. > So what I mean is do something like this: > > u32 flags; /* E.g., CLK_IGNORE_UNUSED */ Do you think u32 flags; /* standard CCF flags */ ls more descriptive? > > +}; > > + > > +struct ccu_factor_config { > > + u32 div; > > + u32 mul; > > +}; > > + > > +struct ccu_mux_config { > > + const u8 *table; > > + u32 flags; > > + u8 shift; > > + u8 width; > > The shift and width fields can be replaced with: > u32 mask; > > Where: > mask = ((1 << width) - 1) << shift; > > > +}; > > + > > +struct ccu_div_config { > > + struct clk_div_table *table; > > + u32 max; > > I'm pretty sure the offset field here is unused and should be removed. Thanks for finding this, I think the flags field in this structure and ccu_mux_config aren't neccesary, either. Will remove them. > > + u32 offset; > > + u32 flags; > > + u8 shift; > > + u8 width; > > The shift and width fields can be replaced with: > u32 mask; > > > +}; > > + > > +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(_gate_mask, _val_enable, _val_disable, _flags) \ > > + (&(struct ccu_gate_config) { \ > > + .gate_mask = _gate_mask, \ > > + .val_enable = _val_enable, \ > > + .val_disable = _val_disable, \ > > + .flags = _flags, \ > > + }) > > + > > +#define CCU_FACTOR_INIT(_div, _mul) \ > > + (&(struct ccu_factor_config) { \ > > + .div = _div, \ > > + .mul = _mul, \ > > + }) > > + > > + > > Pass a mask rather than _shift and _width here, and initialize > the structure accordingly. > > > +#define CCU_MUX_INIT(_shift, _width, _table, _flags) \ > > + (&(struct ccu_mux_config) { \ > > + .shift = _shift, \ > > + .width = _width, \ > > + .table = _table, \ > > + .flags = _flags, \ > > + }) > > + > > Same here, use a mask rather than _shift and _width. > > > +#define CCU_DIV_INIT(_shift, _width, _table, _flags) \ > > + (&(struct ccu_div_config) { \ > > + .shift = _shift, \ > > + .width = _width, \ > > + .flags = _flags, \ > > + .table = _table, \ > > + }) > > + > > +#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) > > + > > I'm almost certain that *all* calls to these *_DEFINE() macros pass > the same name for the _struct value as the _name value, except the > name is quoted. So use the C preprocessor here, and just pass the > non-quoted thing, and use #_struct to stringify it. I'd rather the > name of the argument be _name in that case. This makes sense. Will simplify it. > > +#define CCU_GATE_DEFINE(_struct, _name, _parent, _reg, _gate_mask, \ > > + _val_enable, _val_disable, _flags) \ > > There is one case where the _flags value is CLK_IGNORE_UNUSED, and > one case where it is CLK_IS_CRITICAL. The UART0 (booting UART) and all core clocks are marked as CLK_IS_CRTICAL, not just one case. > All others pass 0. Given > that, you could define two macros and remove the 0 argument from > most of the callers: I'd like to always keep the flags field, since keeping the clock definitions intact and in the same format helps reading. There're a bunch of clocks. > #define CCU_GATE_DEFINE_FLAGS(..., _flags) > > #define CCU_GATE_DEFINE(..., _val_disable) \ > CCU_GATE_DEFINE_FLAGS(..., _val_disable, 0) > > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg, \ > > + .name = _name, \ > > + .num_parents = 1, \ > > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > > + spacemit_ccu_gate_ops, _flags), \ > > + } \ > > +} > > + > > The _mul argument passed to CCU_FACTOR_DEFINE() is always 1. > Just get rid of it, and use 1 for the value used in the macro. Thanks for finding this, will take it. > > +#define CCU_FACTOR_DEFINE(_struct, _name, _parent, _div, _mul) \ > > +struct ccu_mix _struct = { \ > > + .factor = CCU_FACTOR_INIT(_div, _mul), \ > > + .common = { \ > > + .name = _name, \ > > + .num_parents = 1, \ > > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > > + spacemit_ccu_factor_ops, 0), \ > > + } \ > > +} > > + > > Same suggestion as above here, given that _flags is almost always 0. > > Also, pass a mask rather than _shift and _width here. (I'm not going > to repeat this suggestion any more). > > > +#define CCU_MUX_DEFINE(_struct, _name, _parents, _reg, _shift, _width, \ > > + _flags) \ > > +struct ccu_mix _struct = { \ > > + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > > + spacemit_ccu_mux_ops, _flags),\ > > + } \ > > +} > > + > > All three callers of this pass CLK_IS_CRITICAL for _flags. > You could remove the argument and hard-code it, but I think > you might want to change the name of the macro if so. For the reason described above, I'd like to keep all the flags field. > > +#define CCU_DIV_DEFINE(_struct, _name, _parent, _reg, _shift, _width, \ > > + _flags) \ > > +struct ccu_mix _struct = { \ > > + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg, \ > > + .name = _name, \ > > + .num_parents = 1, \ > > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > > + spacemit_ccu_div_ops, _flags) \ > > + } \ > > +} > > + > > +#define CCU_GATE_FACTOR_DEFINE(_struct, _name, _parent, _reg, \ > > + _gate_mask, _val_enable, _val_disable, \ > > You can get rid of _val_enable and _val_disable here; just > use _gate_mask and 0 in their place. > > > + _div, _mul, _flags) \ > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .factor = CCU_FACTOR_INIT(_div, _mul), \ > > + .common = { \ > > + .reg_ctrl = _reg, \ > > + .name = _name, \ > > + .num_parents = 1, \ > > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > > + spacemit_ccu_gate_factor_ops, _flags) \ > > + } \ > > +} > > + > > +#define CCU_MUX_GATE_DEFINE(_struct, _name, _parents, _reg, _shift, \ > > + _width, _gate_mask, _val_enable, \ > > Get rid of _val_enable and _val_disable here too. (I'm not going > to repeat this suggestion any more.) > > > + _val_disable, _flags) \ > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .mux = CCU_MUX_INIT(_shift, _width, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > > + spacemit_ccu_mux_gate_ops, \ > > + _flags), \ > > + } \ > > +} > > + > > +#define CCU_DIV_GATE_DEFINE(_struct, _name, _parent, _reg, _shift, \ > > + _width, _gate_mask, _val_enable, \ > > + _val_disable, _flags) \ > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .div = CCU_DIV_INIT(_shift, _width, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg, \ > > + .name = _name, \ > > + .num_parents = 1, \ > > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > > + spacemit_ccu_div_gate_ops, _flags), \ > > + } \ > > +} > > + > > +#define CCU_DIV_MUX_GATE_DEFINE(_struct, _name, _parents, _reg_ctrl, \ > > + _mshift, _mwidth, _muxshift, _muxwidth, \ > > + _gate_mask, _val_enable, _val_disable, \ > > + _flags) \ > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg_ctrl, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > > + spacemit_ccu_div_mux_gate_ops,\ > > + _flags), \ > > + }, \ > > +} > > + > > I'd put the "2" somewhere else in the name of this. It suggests > "divide by two" but it's really a "second DIV_FC_MUX_GATE" macro. Is DIV_SPLIT_FC_MUX_GATE a better name? The difference between this and DIV_FC_MUX_GATE is that the fc bit locates in a different register. > > +#define CCU_DIV2_FC_MUX_GATE_DEFINE(_struct, _name, _parents, \ > > + _reg_ctrl, _reg_fc, _mshift, \ > > + _mwidth, _fc, _muxshift, _muxwidth, \ > > + _gate_mask, _val_enable, \ > > + _val_disable, _flags) \ > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg_ctrl, \ > > + .reg_fc = _reg_fc, \ > > + .fc = _fc, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > > + spacemit_ccu_div_mux_gate_ops,\ > > + _flags), \ > > + }, \ > > +} > > + > > +#define CCU_DIV_FC_MUX_GATE_DEFINE(_struct, _name, _parents, _reg_ctrl, \ > > + _mshift, _mwidth, _fc, _muxshift, \ > > + _muxwidth, _gate_mask, _val_enable, \ > > + _val_disable, _flags) \ > > The only difference between this and CCU_DIV2_...() above is that > the other macro takes a _reg_fc argument. Just pass _reg_ctrl for > that, and define this macro in terms of the other one. Thanks for the advice! > > +struct ccu_mix _struct = { \ > > + .gate = CCU_GATE_INIT(_gate_mask, _val_enable, \ > > + _val_disable, 0), \ > > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg_ctrl, \ > > + .reg_fc = _reg_ctrl, \ > > + .fc = _fc, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > > + spacemit_ccu_div_mux_gate_ops,\ > > + _flags), \ > > + }, \ > > +} > > + > > +#define CCU_DIV_FC_MUX_DEFINE(_struct, _name, _parents, _reg_ctrl, \ > > + _mshift, _mwidth, _fc, _muxshift, \ > > + _muxwidth, _flags) \ > > +struct ccu_mix _struct = { \ > > + .div = CCU_DIV_INIT(_mshift, _mwidth, NULL, 0), \ > > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg_ctrl, \ > > + .reg_fc = _reg_ctrl, \ > > + .fc = _fc, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > > + spacemit_ccu_div_mux_ops, \ > > + _flags), \ > > + }, \ > > +} > > + > > +#define CCU_MUX_FC_DEFINE(_struct, _name, _parents, _reg_ctrl, _fc, \ > > + _muxshift, _muxwidth, _flags) \ > > +struct ccu_mix _struct = { \ > > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth, NULL, 0), \ > > + .common = { \ > > + .reg_ctrl = _reg_ctrl, \ > > + .reg_fc = _reg_ctrl, \ > > + .fc = _fc, \ > > + .name = _name, \ > > + .num_parents = ARRAY_SIZE(_parents), \ > > + .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..81b929ca1c5c > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_pll.c > > @@ -0,0 +1,189 @@ > > +// 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_MIN_FREQ 600000000 > > +#define PLL_MAX_FREQ 3400000000 > > +#define PLL_DELAY_TIME 3000 > > + > These four values are 1-byte components of what gets placed in > the SWCR1 register, to manage the PLL clocks. They all represent > various things that configure the clock. But in fact, this code > doesn't care at all about the format. All that matters is that > certain combinations of them are well-defined, and produce a > specific clock rate out of the PLL. > > For that reason, I recommend you just do away with this whole notion > of the four bytes being defined separately. Just combine them all > into a single 4-byte value that's sort of an abstract magic number. > Then all of these definitions here can go away. I'll explain more > below. > > > +#define PLL_SWCR1_REG5_OFF 0 > > +#define PLL_SWCR1_REG5_MASK GENMASK(7, 0) > > +#define PLL_SWCR1_REG6_OFF 8 > > +#define PLL_SWCR1_REG6_MASK GENMASK(15, 8) > > +#define PLL_SWCR1_REG7_OFF 16 > > +#define PLL_SWCR1_REG7_MASK GENMASK(23, 16) > > +#define PLL_SWCR1_REG8_OFF 24 > > +#define PLL_SWCR1_REG8_MASK GENMASK(31, 24) > > + > > The SWCR2_* macros can all go away; they're never used (or needed). Indeed, thanks for finding it. > > +#define PLL_SWCR2_DIVn_EN(n) BIT(n + 1) > > +#define PLL_SWCR2_ATEST_EN BIT(12) > > +#define PLL_SWCR2_CKTEST_EN BIT(13) > > +#define PLL_SWCR2_DTEST_EN BIT(14) > > + > > Here too, the "frc" and "int" parts of this register are ignored > by this code. The only thing that matters is that both match a > particular value, and if they do, then they take part in the > production of a specific PLL output rate. So here too I > suggest combining them into a single, 31-bit abstract magic > value. Will do the combination. > The only other thing that matters in the SWCR3 register is > the enable bit. > > > +#define PLL_SWCR3_DIV_FRC_OFF 0 > > +#define PLL_SWCR3_DIV_FRC_MASK GENMASK(23, 0) > > +#define PLL_SWCR3_DIV_INT_OFF 24 > > +#define PLL_SWCR3_DIV_INT_MASK GENMASK(30, 24) > > +#define PLL_SWCR3_EN BIT(31) > > + > > This should be a Boolean function. > > > +static int ccu_pll_is_enabled(struct clk_hw *hw) > > +{ > > + struct ccu_pll *p = hw_to_ccu_pll(hw); > > You could probably do something like hw_to_ccu_comm(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) > > +{ > > + unsigned int reg5, reg6, reg7, reg8, size, i; > > + unsigned int div_int, div_frc; > > + struct ccu_pll_rate_tbl *freq_pll_regs_table; > > + struct ccu_pll *p = hw_to_ccu_pll(hw); > > + struct ccu_common *common = &p->common; > > + u32 tmp; > > + > > + ccu_read(swcr1, common, &tmp); > > + reg5 = (tmp & PLL_SWCR1_REG5_MASK) >> PLL_SWCR1_REG5_OFF; > > + reg6 = (tmp & PLL_SWCR1_REG6_MASK) >> PLL_SWCR1_REG6_OFF; > > + reg7 = (tmp & PLL_SWCR1_REG7_MASK) >> PLL_SWCR1_REG7_OFF; > > + reg8 = (tmp & PLL_SWCR1_REG8_MASK) >> PLL_SWCR1_REG8_OFF; > > Here you're extracting the four bytes out of the value read from > the SWCR1 register. > > > + > > + ccu_read(swcr3, common, &tmp); > > And here you're extracting the INT and FRC parts of the value > read from the SWCR3 register. > > > + div_int = (tmp & PLL_SWCR3_DIV_INT_MASK) >> PLL_SWCR3_DIV_INT_OFF; > > + div_frc = (tmp & PLL_SWCR3_DIV_FRC_MASK) >> PLL_SWCR3_DIV_FRC_OFF; > > + > > + freq_pll_regs_table = p->pll.rate_tbl; > > + size = p->pll.tbl_size; > > + > > + for (i = 0; i < size; i++) > > Then the only thing that matters is whether all four values in > the table entry are equal to the four extracted values, *and* > the div and frc values match as well. > > Just do a single 4-byte comparision of the value read from SWCR1, > and also a comparison of the value read from SWCR3 (with the enable > bit masked off). > > > + if ((freq_pll_regs_table[i].reg5 == reg5) && > > + (freq_pll_regs_table[i].reg6 == reg6) && > > + (freq_pll_regs_table[i].reg7 == reg7) && > > + (freq_pll_regs_table[i].reg8 == reg8) && > > + (freq_pll_regs_table[i].div_int == div_int) && > > + (freq_pll_regs_table[i].div_frac == div_frc)) > > + return freq_pll_regs_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_base, > > + 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); > > +} > > + > > +/* > > + * pll rate change requires sequence: > > + * clock off -> change rate setting -> clock on > > + * This function doesn't really change rate, but cache the config > > Actually, the function *does* seem to change the rate. > (Fix this comment.) Okay. > > + */ > > +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; > > + struct ccu_pll_rate_tbl *entry = NULL; > > + u32 mask, val; > > + int i; > > + > > + for (i = 0; i < params->tbl_size; i++) { > > + if (rate == params->rate_tbl[i].rate) { > > + entry = ¶ms->rate_tbl[i]; > > + break; > > + } > > + } > > + > > + if (WARN_ON_ONCE(!entry)) > > + return -EINVAL; > > + > > + mask = PLL_SWCR1_REG5_MASK | PLL_SWCR1_REG6_MASK; > > + mask |= PLL_SWCR1_REG7_MASK | PLL_SWCR1_REG8_MASK; > > + val = entry->reg5 << PLL_SWCR1_REG5_OFF; > > + val |= entry->reg6 << PLL_SWCR1_REG6_OFF; > > + val |= entry->reg7 << PLL_SWCR1_REG7_OFF; > > + val |= entry->reg8 << PLL_SWCR1_REG8_OFF; > > + ccu_update(swcr1, common, mask, val); > > + > > + mask = PLL_SWCR3_DIV_INT_MASK | PLL_SWCR3_DIV_FRC_MASK; > > + val = entry->div_int << PLL_SWCR3_DIV_INT_OFF; > > + val |= entry->div_frac << PLL_SWCR3_DIV_FRC_OFF; > > + ccu_update(swcr3, common, mask, val); > > + > > + 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 long max_rate = 0; > > unsigned long long max_rate = 0; > > (At least that's how it's defined in the ccu_pll_rate_tbl structure.) Will change the member in ccu_pll_rate_tbl to unsigned long. > > + unsigned int i; > > + > > + for (i = 0; i < params->tbl_size; i++) { > > + if (params->rate_tbl[i].rate <= rate) { > > + if (max_rate < params->rate_tbl[i].rate) > > + max_rate = params->rate_tbl[i].rate; > > + } > > + } > > + > > I don't like that you're passing a compile-time defined > frequency (which might not match reality). We could make the rate_tbl sorted, which should improve both set_rate and round_rate a lot. > > + return MAX(max_rate, PLL_MIN_FREQ); > > +} > > + > > +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..d3397120414e > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu_pll.h > > @@ -0,0 +1,80 @@ > > +/* 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 { > > I'd prefer you define this as u64. But if you meant to match what > the clock routines use, I think that's unsigned long (not long long). > > > + unsigned long long rate; > > + u32 reg5; > > + u32 reg6; > > + u32 reg7; > > + u32 reg8; > > + unsigned int div_int; > > + unsigned int div_frac; > > +}; > > + > > +struct ccu_pll_config { > > The rate_tbl is probably const, isn't it? > > > + struct ccu_pll_rate_tbl *rate_tbl; > > + u32 tbl_size; > > + u32 reg_lock; > > + u32 lock_enable_bit; > > +}; > > + > > Here I recommend you define two combined magic values rather than > all these arguments. > > If reg5-reg8 are 0x67, 0xdd, 0x50, 0x00, then pass 0x0050dd67. > > And if div_int is 0x43 and div_frac is 0xeaaaab, pass 0x43eaaaab. > > > +#define CCU_PLL_RATE(_rate, _reg5, _reg6, _reg7, _reg8, _div_int, _div_frac) \ > > + { \ > > + .rate = (_rate), \ > > + .reg5 = (_reg5), \ > > + .reg6 = (_reg6), \ > > + .reg7 = (_reg7), \ > > + .reg8 = (_reg8), \ > > + .div_int = (_div_int), \ > > + .div_frac = (_div_frac), \ > > + } > > + > > +struct ccu_pll { > > + struct ccu_pll_config pll; > > + struct ccu_common common; > > +}; > > + > > +#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \ > > + { \ > > It shouldn't be necessary to type-cast the table here. I think I > saw this sort of thing elsewhere and didn't comment on it (and if > that's true, fix those too). > > That's the end! Really thanks for the detailed review! > -Alex Best regards, Haylen Chu [1]: https://developer.spacemit.com/documentation?token=LCrKwWDasiJuROkVNusc2pWTnEb#part603 [2]: https://developer.spacemit.com/documentation?token=LCrKwWDasiJuROkVNusc2pWTnEb#part271 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-02-16 11:34 ` Haylen Chu @ 2025-02-21 21:10 ` Alex Elder 2025-02-22 9:57 ` Haylen Chu 2025-02-22 9:40 ` Haylen Chu 1 sibling, 1 reply; 31+ messages in thread From: Alex Elder @ 2025-02-21 21:10 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, Inochi Amaoto, Chen Wang, Guodong Xu On 2/16/25 5:34 AM, Haylen Chu wrote: > On Thu, Feb 13, 2025 at 10:04:10PM -0600, Alex Elder wrote: >> On 1/3/25 3:56 PM, Haylen Chu wrote: >>> The clock tree of K1 SoC contains three main types of clock hardware >>> (PLL/DDN/MIX) and is managed by several independent controllers in >>> different SoC parts (APBC, APBS and etc.), thus different compatible >>> strings are added to distinguish them. >>> >>> Some controllers may share IO region with reset controller and other low >>> speed peripherals like watchdog, so all register operations are done >>> through regmap to avoid competition. >>> >>> Signed-off-by: Haylen Chu <heylenay@4d2.org> >> >> This is a really big patch (over 3000 lines), and a fairly large >> amount of code to review. But I've given it a really thorough >> read and I have a *lot* of review comments for you to consider. > > Thanks for the detailed review! > >> First, a few top-level comments. >> - This driver is very comprehensive. It represents essentially >> *all* of the clocks in the tree diagram shown here: >> https://developer.spacemit.com/resource/file/images?fileName=DkWGb4ed7oAziVxE6PIcbjTLnpd.png >> (I can tell you what's missing but I don't think it matters.) >> - In almost all cases, the names of the clocks match the names >> shown in that diagram, which is very helpful. >> - All of the clocks are implemented using "custom" clock >> implementations. I'm fairly certain that almost all of >> them can use standard clock framework types instead >> (fixed-rate, fixed-factor, fractional-divider, mux, and >> composite). But for now I think there are other things >> more important to improve. > > These three types of clocks are originally adapted from the vendor > driver, although the mix type has been largely rewritten during the > revisions. > > If the custom types don't cause big problems, I'd like to see the driver > merged first and we can gradually convert them to the more general ones, > which avoids blocking other SoC drivers. Understood. Actually I have some other input that suggests repreenting things differently, but let me respond to your message (this one) first. . . . >> What do APBS, MPMU, APBC, and APMU stand for and represent? > > APMU and MPMU are abbreviated from Application/Main Power Management > Unit, as mentioned earlier in my reply to the dt-binding things. APBC > means APB Bus Clock Unit. APBS stands for APB SPARE and I'm not sure > about the precise meaning. > > These information seems important but I'm not sure about where they > should be documented. I could mention it in the cover letter or commit > message in next version. I guess knowing what they stand for is something, but knowing why they're separated and what function each plays is more important. Don't worry about this for now. >> It seems each has its own clock controller, but can you >> offer any explanation about why they're divided that way? > > Sadly no, I have no idea why the clock bits are divided into several > MMIO regions, which really complicates binding. . . . >>> +/* APMU register offset */ >>> +#define APMU_CCI550_CLK_CTRL 0x300 >>> +#define APMU_CPU_C0_CLK_CTRL 0x38C >>> +#define APMU_CPU_C1_CLK_CTRL 0x390 I didn't notice previously, but the above three are also duplicated a little later. >>> +#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 >> >> APMU_USB_CLK_RES_CTRL is duplicated below. > > Thanks, will remove one along with other duplication. > > For the unused offsets, I prefer to keep the documented ones for future > completion of the driver. I don't think it's a large burden. That's fine. Almost everything is used, so it does no real harm to include the few that aren't (yet). >>> +#define APMU_USB_CLK_RES_CTRL 0x5c >>> +#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 . . . >> I'm going to say more about this below, but these definitions can >> be simplified a lot. For example, the first one could be: >> CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd), >> >>> +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = { >>> + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), >>> +}; >>> + >>> +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { >>> + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), >>> +}; >>> + >>> +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { >>> + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), >>> + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), >>> + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), >>> +}; >>> + >> >> Therre are only three PLL clocks, defined next. All share the same >> lock register offset (MPMU_POSR), so that could be factored out. > > I'm not sure how to factor the offset out, could you please explain more > about it? By coding the offset in the macro or something else? > > I'd like to keep it as is, since PLL status in future SpacemiT SoCs are > likely to locate in different registers. Factoring the offset out won't > save a lot of characters, either. Keep it the way you have it. Factoring it out doesn't help much. >>> +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, >>> + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, >>> + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); . . . >> The next clock is weird, and it's the only one of its kind. It is not >> represented in the clock tree diagram. It is a "factor 1" clock (so it >> just passes the parent's rate through), and has no gate. Do you know >> why it's defined? It is used only as one of the MPMU parent clocks. >> Why isn't just the pll1_d7 clock used in its place? > > It is represented in the diagram. The photo version of the diagram seems > hard to search so I will ask the vendor to publish a PDF version if > possible. > > As the definition involves no hardware bits, I guess it's actually an > alias listed to keep the tree structure in similar form. Will confirm > this with the vendor. This is what I meant by "not represented in the diagram." It seems like it's just a place holder, more or less. I think this is the only one defined here that's like that. >>> +static CCU_FACTOR_DEFINE(pll1_d7_351p08, "pll1_d7_351p08", CCU_PARENT_HW(pll1_d7), >>> + 1, 1); . . . >>> +static struct ccu_ddn_tbl slow_uart1_tbl[] = { >>> + { .num = 125, .den = 24 }, >>> +}; >>> +static struct ccu_ddn_tbl slow_uart2_tbl[] = { >>> + { .num = 6144, .den = 960 }, >>> +}; >> >> I'll note here that this "slow_uart" gate is defined separately >> from the two slow UART DDN clocks below. That's different from >> some other gated clocks. > > "slow_uart" is a gate that controlls both slow_uart1_14p7 and > slow_uart2_48. Enabling any one of these two clocks requires slow_uart > to ungate. > > I didn't find a good method to describe the structure under CCF (I'm > really new to it), thus listed it out and applied the CLK_IGNORE_UNUSED > trick. Any suggesions on this will be appreciated, thanks. Right now I have no suggestions. Let's see the next version of the patches and we can maybe revisit this. >>> +static CCU_GATE_DEFINE(slow_uart, "slow_uart", CCU_PARENT_NAME(osc), >>> + MPMU_ACGR, >>> + BIT(1), BIT(1), 0, CLK_IGNORE_UNUSED); >> >> What follows are the only two DDN clocks defined. Both define >> a "table" of numerator/denominator pairs, but in both cases, >> the table has only one entry. >> >> Given that, why not simply encode the numerator and denominator >> in the ccu_info structure for each of these DDN clock instances? > > DDN types are M/N clocks, just like clk-fractional-divider. Hardcoded > tables (even with only one entry) work because their only consumers are > UARTs, which require a relatively fixed frequency. This will prevent us > from generating precise clock if we want the UART to operate under a > different baudrate. > > I'll cover more about this later. My point was that there's no need to define a "table" if there's only one entry. >>> +static CCU_DDN_DEFINE(slow_uart1_14p74, "slow_uart1_14p74", pll1_d16_153p6, >>> + &uart_ddn_mask_info, slow_uart1_tbl, >>> + MPMU_SUCCR, 0); >>> +static CCU_DDN_DEFINE(slow_uart2_48, "slow_uart2_48", pll1_d4_614p4, >>> + &uart_ddn_mask_info, slow_uart2_tbl, >>> + MPMU_SUCCR_1, 0); >>> + >>> +static CCU_GATE_DEFINE(wdt_clk, "wdt_clk", CCU_PARENT_HW(pll1_d96_25p6), >>> + MPMU_WDTPCR, >>> + BIT(1), BIT(1), 0x0, >>> + 0); >>> + >> >> I couldn't find the "ripc_clk" on the clock tree diagram. It is >> never used elsewhere, so I think this definition can go away. > > I'm not sure whether the ripc_clk doesn't exist or it's just missing in > both datasheet and clock tree diagram. Will confirm with the vendor. > >>> +static CCU_GATE_DEFINE(ripc_clk, "ripc_clk", CCU_PARENT_NAME(vctcxo_24m), >>> + MPMU_RIPCCR, >>> + 0x3, 0x3, 0x0, >>> + 0); >>> + >>> +static CCU_GATE_FACTOR_DEFINE(i2s_sysclk, "i2s_sysclk", CCU_PARENT_HW(pll1_d16_153p6), >>> + MPMU_ISCCR, >>> + BIT(31), BIT(31), 0x0, 50, 1, >>> + 0); >>> +static CCU_GATE_FACTOR_DEFINE(i2s_bclk, "i2s_bclk", CCU_PARENT_HW(i2s_sysclk), >>> + MPMU_ISCCR, >>> + BIT(29), BIT(29), 0x0, 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_clk", apb_parents, >>> + MPMU_APBCSCR, >>> + 0, 2, >>> + 0); >>> + >> >> The following clock is just called "wdt_clk" on the clock tree diagram. > > They're different things. The wdt_clk is defined a few lines earlier and > acts as the function clock of watchdog. Instead, wdt_bus_clk is its bus > clock. > > Most bus clocks in the clock tree diagram aren't listed explicitly, but > represented as a gate besides the function clock. OK, this is good to know. Thank you. > >>> +static CCU_GATE_DEFINE(wdt_bus_clk, "wdt_bus_clk", CCU_PARENT_HW(apb_clk), >>> + MPMU_WDTPCR, >>> + BIT(2), BIT(2), 0x0, >>> + 0); > > I do find a typo here. BIT(0) should be used here instead of BIT(2), > which is the reset bit for watchdog. > >>> +/* 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), >>> +}; >> >> I might be misunderstanding this, but all of the 9 UART clocks below >> share a common gate. > > AFAIK they don't share the common gate. Each uart comes with its own > functional gate, bus gate and multiplexer. The diagram seems a little > confusing: only one gate is drawn to avoid duplication. OK this is also good to know, thanks. > This applies for PWM as well. There're 10 different UART clock/reset > registers, each contains a gate bit for functional clock and one for > bus clock, confirming the idea[1]. > >> I *think* that gate should be represented as a distinct clock so that >> it can be properly reference-counted. There are numerous cases of this. After I sent this I concluded the above statement was wrong. >> Also note there is no uart1 clock; I think the clock tree diagram >> erroneously calls the first clock "uart1" (not uart0). > > The clocks are reordered to match our current devicetree, where the > first uart is uart0 and uart1 is assigned to the one in RCPU (real-time > CPU) region. I guess what I care about is that it is done in a way that avoids any confusion. I believe what you describe makes sense. >>> +static CCU_MUX_GATE_DEFINE(uart0_clk, "uart0_clk", uart_clk_parents, >>> + APBC_UART1_CLK_RST, >>> + 4, 3, BIT(1), BIT(1), 0x0, >>> + CLK_IS_CRITICAL); . . . >> Here we begin a bunch of definitions of "bus clocks". They are all >> simply gates, and the clock tree diagram shows these paired with >> another gated parent. > > Please note in the diagram, the clock beside the bus clock isn't the > parent of the bus clock. Without special notes, all bus clocks in the > APBC region take apb_clk as parent. All bus clocks in the APMU region > take pmua_aclk as parent. OK, I think there might be a note at the top of the diagram that suggests this, but I'm very happy to have you explain it to me. >> This says to me that in order to use a "child clock" in this >> situation, both the "regular gate" and the "bus gate" clock must be >> defined, and a reference to it taken (with clk_get() or similar). > > No, functional clock and bus clock are completely irrelevant. They > control different parts of a peripheral. Taking UART as example, you do > need to take both function and bus clock for normal operation; but if > the bus clock is disabled, the UART could continue to send data. Only > the bus component won't work and the registers couldn't be accessed. The bus clocks sound like interconnects. But I'm not going to even go there right now... >> Can you confirm this? >>> + . . . >> The next two parent clocks are duplicates. It looks this way on the >> clock tree diagram as well. Is this correct? Can you find out from >> SpacemiT whether one of them is actually a different clock (like >> pll2_d6 or something)? It makes no sense to have two multiplexed >> parent clocks with the same source. > > Yes, will confirm it later. The register description[2] suggests it's > wrong (there aren't two configuration for MIPI_BIT_CLK_SEL resulting in > the same frequency). > >>> + CCU_PARENT_HW(pll2_d8), >>> + CCU_PARENT_HW(pll2_d8), >>> +}; OK thank you. >>> +static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, "dpu_bit_clk", dpubit_parents, >>> + APMU_LCD_CLK_RES_CTRL1, >>> + 17, 3, BIT(31), >>> + 20, 3, BIT(16), BIT(16), 0x0, >>> + 0); . . . >>> +static CCU_DIV_FC_MUX_GATE_DEFINE(sdh1_clk, "sdh1_clk", sdh01_parents, >>> + APMU_SDH1_CLK_RES_CTRL, >>> + 8, 3, BIT(11), >>> + 5, 3, BIT(4), BIT(4), 0x0, >>> + 0); >> >> This is strange too. sdh2_parents is identical to sdh01_parents. > > No, it isn't. pll2_d5 is a parent for sdh0/1; for sdh2, pll1_d3_819p2 > replaces it. I see that now in the code, but not in the diagram. I guess I'll assume the code is right. >> All of the bits used are identical for the three control registers. >> The clock tree diagram shows all three of these sdhx clocks sharing >> a single parent mux. Why is sdh2_parents defined separately? > > I don't think the mux is shared. It should be the same case for > UART/PWM clocks, as described earlier. I'm sure you're right. >>> +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), >>> +}; . . . >> You could reduce some duplication here using a simple >> macro for defining the address of the clk_hw structure: >> >> #define CLK_HW(x) &x.common.hw >> >> (Add parentheses if you like.) It saves 3 characters for >> each clock... > > Looks good to me, but Don't bother for now. It can be fixed easily later if it's important. >>> +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { >>> + .hws = { >>> + [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, >>> + >>> + }, >>> + .num = CLK_APBS_NUM, >> >> It sure would be nice to be able to use ARRAY_SIZE() here but >> alas I don't think it's possible here. > > CLK_*_NUM will be removed in the next revision, as it's actually not a > binding. I'm looking for a better way to represent these clocks. > > Maybe represent the clocks with (ID, struct clk_hw *) pairs and > generate struct clk_hw_onecell_data dynamically during probe. Yes I think I have a suggestion but I'll hold off until I send out another message in which I suggest something different. >>> +}; . . . >> The only time the "PLL lock" is needed is with PLL clocks. Can you >> encode this flag, or perhaps the address of the lock register, >> into the ccu_pll structure instead somehow? >> >> If so you wouldn't need this spacemit_ccu_data structure, and could >> simply use the clk_hw_onecell_data structure in its place. > > We could scan the clk_hw_onecell_data array before the probing, but I > don't think it's worth. > > As the raw clk_hw_onecell_data is likely to be removed in the next > revision, I'll try to avoid the flag during refactoring. I think you'll be able to. >>> +struct spacemit_ccu_data { >>> + struct clk_hw_onecell_data *hw_clks; >>> + bool need_pll_lock; >>> +}; . . . >> The next two pointers are regmap pointers. Could the >> name of the fields suggest that? (They sound like raw >> memory addresses to me.) > > Do "common.regmap" and "common.lock_regmap" sound better for you? Yes I think that's an improvement. >>> + common->base = priv->base; >>> + common->lock_base = priv->lock_base; . . . >>> + ret = devm_clk_hw_register(dev, hw); >>> + if (ret) { >>> + dev_err(dev, "Cannot register clock %d - %s\n", >>> + i, name); >> >> Just use hw->init->name here instead of name. > > Will change in the next version. . . . >> You allocate the priv structure, but never free it (a bug). >> It only holds 3 values; just pass them as arguments when >> registering the CCU, as suggested above. > > The allocation is done through a devres variant, it should be released > automatically. Please correct me if I'm wrong. You're right. But my other point about priv being unnecessary (just pass the three values as arguments) remains. >>> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); >>> + if (!priv) >>> + return -ENOMEM; . . . >> End of "ccu-k1.c"!!! > > It's really a lot of lines lol > >>> diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h >>> new file mode 100644 >>> index 000000000000..242461ee592f >>> --- /dev/null >>> +++ b/drivers/clk/spacemit/ccu_common.h >>> @@ -0,0 +1,51 @@ . . . >> Although I think the following macros are nice and concise, they >> obscure the fact that they're essentially renaming existing >> regmap operations. They don't really add much value; the >> regmap functions are pretty clear. > > They're just for saving some characters, > > regmap_read(common->base, common->reg_ctrl, &tmp) > > is much longer than > > ccu_read(common, ctrl, &tmp) > > Of course it could be changed, if you're still strongly against it, It's OK for now, but I might make the same suggestion in a future version of the series. >> Although regmap_read() returns a value, you never check for an >> error when calling ccu_read(). > > I don't expect that access to a MMIO regmap could fail. There shouldn't > be any cases of failure, so I don't check it. > >>> +#define ccu_read(reg, c, val) regmap_read((c)->base, (c)->reg_##reg, val) It should never return an error. But what if it did? I'm mainly thinking about the very first attempt to read from the I/O space. It's probably OK; lots of existing code ignores any return value from regmap_read. . . . >>> diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c >>> new file mode 100644 >>> index 000000000000..1df555888ecb >>> --- /dev/null >>> +++ b/drivers/clk/spacemit/ccu_ddn.c . . . >> The next three functions are never used and can be removed. >> >> The reason is that the only time they'd be used is when >> CCU_DDN_GATE_DEFINE() (defined in "ccu_ddn.h") is called, >> but it never is. So you can get rid of CCU_DDN_GATE_DEFINE(), >> and once you do that there's no need for ccu_ddn_disable(), >> ccu_ddn_enable(), and ccu_ddn_is_enabled(). Furthermore, >> these three functions are the only place the "reg_sel" >> field in the ccu_common structure is used, and without >> these functions, that field serves no purpose. So that >> field can go away, and there's no need to provide a value >> for initializing it in all those clock definition macros. > > When cleaning up the driver from the vendor source, I tried to keep most > clock types included since future SpacemiT SoCs made may make use of > them. It seems too farsighted as this patch is already quite large. > > I'll remove the unused clock types as well as the reg_sel field, thanks. This is good practice. Don't add code that you expect to use at a future date. Keep that out, and add it in *when* you need it. Otherwise it's basically dead code. . . . >> I'm going to say more about this below, but I think the >> factor can be removed and hard-coded here. > > Alright, we could hardcode it for now to make the patch as simple as > possible. If different factors are used in future SoCs, it isn't hard > to add it back later. Exactly. >>> + rate = (*prate * params->tbl[i].den) / >>> + (params->tbl[i].num * params->info->factor); >> . . . >> Here is a general comment that applies to a bunch of places in >> this code. You can concisely define a field with a single >> mask value. That means there is no need to define both a mask >> (or a field width) and a shift. What you need is defined in >> <linux/bitfield.h>. There you will find a set of macros >> like FIELD_GET() and FIELD_PREP(), or alternatively a set >> of inline functions like u32_get_bits() and u32_encode_bits(). > > I think both macros and inline functions in bitfield.h work with only > constant masks. With non-constant masks, some checks cannot be optimized > out, resulting in compilation errors. Yes, this was true some time back and I suppose it still is. For some reason I thought there might have been a way to use non-constant masks. Anyway, I still think something similar to u32_get_bits() or FIELD_GET() is nicely encapsulated and less error-prone than "manually" shifting and masking. Don't worry about it for now, but again I might suggest it again. >>> + unsigned int num_mask; >>> + unsigned int den_mask; >>> + unsigned int num_shift; >>> + unsigned int den_shift; >>> +}; . . . >>> diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c >>> new file mode 100644 >>> index 000000000000..b46eeb59faea >>> --- /dev/null >>> +++ b/drivers/clk/spacemit/ccu_mix.c . . . >>> +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; >>> + >>> + if (mux->table) >> >> You should verify that the index is less than the >> number of entries in the table before you blindly >> dereference it here. > > The desired index is found by searching in clk_fetch_parent_index(), > thus is always be valid, isn't it? > > However, this is a dead codepath: no clock hardware comes with a valid > table. The whole if could be removed for simplication. That's even better. >>> + index = mux->table[index]; . . . >>> + u32 val_enable; >>> + u32 val_disable; >>> + u32 flags; >> >> When you have a flags field like this, it's helpful to add a comment >> that indicates what values it can take on. I believe in all cases >> like this, the flags values are standard clock framework flags. > > Yes. > >> So what I mean is do something like this: >> >> u32 flags; /* E.g., CLK_IGNORE_UNUSED */ > > Do you think > > u32 flags; /* standard CCF flags */ > > ls more descriptive? No, but your way is OK. > >>> +}; . . . >>> +#define CCU_GATE_DEFINE(_struct, _name, _parent, _reg, _gate_mask, \ >>> + _val_enable, _val_disable, _flags) \ >> >> There is one case where the _flags value is CLK_IGNORE_UNUSED, and >> one case where it is CLK_IS_CRITICAL. > > The UART0 (booting UART) and all core clocks are marked as > CLK_IS_CRTICAL, not just one case. > >> All others pass 0. Given >> that, you could define two macros and remove the 0 argument from >> most of the callers: > > I'd like to always keep the flags field, since keeping the clock > definitions intact and in the same format helps reading. There're a > bunch of clocks. OK. . . . >> I'd put the "2" somewhere else in the name of this. It suggests >> "divide by two" but it's really a "second DIV_FC_MUX_GATE" macro. > > Is DIV_SPLIT_FC_MUX_GATE a better name? The difference between this and > DIV_FC_MUX_GATE is that the fc bit locates in a different register. Either is fine. My point was about "DIV2" suggesting "divide by two". . . . >>> diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c >>> new file mode 100644 >>> index 000000000000..81b929ca1c5c >>> --- /dev/null >>> +++ b/drivers/clk/spacemit/ccu_pll.c . . . >>> + unsigned int i; >>> + >>> + for (i = 0; i < params->tbl_size; i++) { >>> + if (params->rate_tbl[i].rate <= rate) { >>> + if (max_rate < params->rate_tbl[i].rate) >>> + max_rate = params->rate_tbl[i].rate; >>> + } >>> + } >>> + >> >> I don't like that you're passing a compile-time defined >> frequency (which might not match reality). > > We could make the rate_tbl sorted, which should improve both set_rate > and round_rate a lot. That might be nice, but it's not strictly necessary. But if you don't do that, fix the loop above. >>> + return MAX(max_rate, PLL_MIN_FREQ); >>> +} . . . That's the end of my response. Now on to my bigger proposal. Do *not* delay posting the next version of your series based on my next message. Just send out an update that addresses all of the feedback you have received so far. If my proposal makes sense we can talk about how to modify that next version to incorporate it. Thanks. -Alex ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-02-21 21:10 ` Alex Elder @ 2025-02-22 9:57 ` Haylen Chu 0 siblings, 0 replies; 31+ messages in thread From: Haylen Chu @ 2025-02-22 9:57 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, Inochi Amaoto, Chen Wang, Guodong Xu On Fri, Feb 21, 2025 at 03:10:46PM -0600, Alex Elder wrote: > On 2/16/25 5:34 AM, Haylen Chu wrote: > > On Thu, Feb 13, 2025 at 10:04:10PM -0600, Alex Elder wrote: > > > On 1/3/25 3:56 PM, Haylen Chu wrote: > > > > The clock tree of K1 SoC contains three main types of clock hardware > > > > (PLL/DDN/MIX) and is managed by several independent controllers in > > > > different SoC parts (APBC, APBS and etc.), thus different compatible > > > > strings are added to distinguish them. > > > > > > > > Some controllers may share IO region with reset controller and other low > > > > speed peripherals like watchdog, so all register operations are done > > > > through regmap to avoid competition. > > > > > > > > Signed-off-by: Haylen Chu <heylenay@4d2.org> > > > > > > This is a really big patch (over 3000 lines), and a fairly large > > > amount of code to review. But I've given it a really thorough > > > read and I have a *lot* of review comments for you to consider. > > > > Thanks for the detailed review! > > > > > First, a few top-level comments. > > > - This driver is very comprehensive. It represents essentially > > > *all* of the clocks in the tree diagram shown here: > > > https://developer.spacemit.com/resource/file/images?fileName=DkWGb4ed7oAziVxE6PIcbjTLnpd.png > > > (I can tell you what's missing but I don't think it matters.) > > > - In almost all cases, the names of the clocks match the names > > > shown in that diagram, which is very helpful. > > > - All of the clocks are implemented using "custom" clock > > > implementations. I'm fairly certain that almost all of > > > them can use standard clock framework types instead > > > (fixed-rate, fixed-factor, fractional-divider, mux, and > > > composite). But for now I think there are other things > > > more important to improve. > > > > These three types of clocks are originally adapted from the vendor > > driver, although the mix type has been largely rewritten during the > > revisions. > > > > If the custom types don't cause big problems, I'd like to see the driver > > merged first and we can gradually convert them to the more general ones, > > which avoids blocking other SoC drivers. > > Understood. Actually I have some other input that suggests > repreenting things differently, but let me respond to your > message (this one) first. > > . . . > > > > What do APBS, MPMU, APBC, and APMU stand for and represent? > > > > APMU and MPMU are abbreviated from Application/Main Power Management > > Unit, as mentioned earlier in my reply to the dt-binding things. APBC > > means APB Bus Clock Unit. APBS stands for APB SPARE and I'm not sure > > about the precise meaning. > > > > These information seems important but I'm not sure about where they > > should be documented. I could mention it in the cover letter or commit > > message in next version. > > I guess knowing what they stand for is something, but knowing > why they're separated and what function each plays is more > important. Don't worry about this for now. > > > > It seems each has its own clock controller, but can you > > > offer any explanation about why they're divided that way? > > > > Sadly no, I have no idea why the clock bits are divided into several > > MMIO regions, which really complicates binding. > > . . . > > > > > +/* APMU register offset */ > > > > +#define APMU_CCI550_CLK_CTRL 0x300 > > > > +#define APMU_CPU_C0_CLK_CTRL 0x38C > > > > +#define APMU_CPU_C1_CLK_CTRL 0x390 > > I didn't notice previously, but the above three are also > duplicated a little later. > > > > > +#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 > > > > > > APMU_USB_CLK_RES_CTRL is duplicated below. > > > > Thanks, will remove one along with other duplication. > > > > For the unused offsets, I prefer to keep the documented ones for future > > completion of the driver. I don't think it's a large burden. > > That's fine. Almost everything is used, so it does no real > harm to include the few that aren't (yet). > > > > > +#define APMU_USB_CLK_RES_CTRL 0x5c > > > > +#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 > > . . . > > > > I'm going to say more about this below, but these definitions can > > > be simplified a lot. For example, the first one could be: > > > CCU_PLL_RATE(2457600000UL, 0x0050dd64, 0x330ccccd), > > > > > > > +static const struct ccu_pll_rate_tbl pll1_rate_tbl[] = { > > > > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > > > > +}; > > > > + > > > > +static const struct ccu_pll_rate_tbl pll2_rate_tbl[] = { > > > > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > > > > +}; > > > > + > > > > +static const struct ccu_pll_rate_tbl pll3_rate_tbl[] = { > > > > + CCU_PLL_RATE(3000000000UL, 0x66, 0xdd, 0x50, 0x00, 0x3f, 0xe00000), > > > > + CCU_PLL_RATE(3200000000UL, 0x67, 0xdd, 0x50, 0x00, 0x43, 0xeaaaab), > > > > + CCU_PLL_RATE(2457600000UL, 0x64, 0xdd, 0x50, 0x00, 0x33, 0x0ccccd), > > > > +}; > > > > + > > > > > > Therre are only three PLL clocks, defined next. All share the same > > > lock register offset (MPMU_POSR), so that could be factored out. > > > > I'm not sure how to factor the offset out, could you please explain more > > about it? By coding the offset in the macro or something else? > > > > I'd like to keep it as is, since PLL status in future SpacemiT SoCs are > > likely to locate in different registers. Factoring the offset out won't > > save a lot of characters, either. > > Keep it the way you have it. Factoring it out doesn't help much. > > > > > +static CCU_PLL_DEFINE(pll1, "pll1", pll1_rate_tbl, > > > > + APB_SPARE1_REG, APB_SPARE2_REG, APB_SPARE3_REG, > > > > + MPMU_POSR, POSR_PLL1_LOCK, CLK_SET_RATE_GATE); > > . . . > > > > The next clock is weird, and it's the only one of its kind. It is not > > > represented in the clock tree diagram. It is a "factor 1" clock (so it > > > just passes the parent's rate through), and has no gate. Do you know > > > why it's defined? It is used only as one of the MPMU parent clocks. > > > Why isn't just the pll1_d7 clock used in its place? > > > > It is represented in the diagram. The photo version of the diagram seems > > hard to search so I will ask the vendor to publish a PDF version if > > possible. > > > > As the definition involves no hardware bits, I guess it's actually an > > alias listed to keep the tree structure in similar form. Will confirm > > this with the vendor. > > This is what I meant by "not represented in the diagram." It seems > like it's just a place holder, more or less. I think this is the > only one defined here that's like that. Yes, it's really a placeholder, as confirmed in another reply sent earlier. Will remove it in the next version. > > > > +static CCU_FACTOR_DEFINE(pll1_d7_351p08, "pll1_d7_351p08", CCU_PARENT_HW(pll1_d7), > > > > + 1, 1); > > . . . > > > > > +static struct ccu_ddn_tbl slow_uart1_tbl[] = { > > > > + { .num = 125, .den = 24 }, > > > > +}; > > > > +static struct ccu_ddn_tbl slow_uart2_tbl[] = { > > > > + { .num = 6144, .den = 960 }, > > > > +}; > > > > > > I'll note here that this "slow_uart" gate is defined separately > > > from the two slow UART DDN clocks below. That's different from > > > some other gated clocks. > > > > "slow_uart" is a gate that controlls both slow_uart1_14p7 and > > slow_uart2_48. Enabling any one of these two clocks requires slow_uart > > to ungate. > > > > I didn't find a good method to describe the structure under CCF (I'm > > really new to it), thus listed it out and applied the CLK_IGNORE_UNUSED > > trick. Any suggesions on this will be appreciated, thanks. > > Right now I have no suggestions. Let's see the next version > of the patches and we can maybe revisit this. Okay. > > > > +static CCU_GATE_DEFINE(slow_uart, "slow_uart", CCU_PARENT_NAME(osc), > > > > + MPMU_ACGR, > > > > + BIT(1), BIT(1), 0, CLK_IGNORE_UNUSED); > > > > > > What follows are the only two DDN clocks defined. Both define > > > a "table" of numerator/denominator pairs, but in both cases, > > > the table has only one entry. > > > > > > Given that, why not simply encode the numerator and denominator > > > in the ccu_info structure for each of these DDN clock instances? > > > > DDN types are M/N clocks, just like clk-fractional-divider. Hardcoded > > tables (even with only one entry) work because their only consumers are > > UARTs, which require a relatively fixed frequency. This will prevent us > > from generating precise clock if we want the UART to operate under a > > different baudrate. > > > > I'll cover more about this later. > > My point was that there's no need to define a "table" if there's > only one entry. You're right. I've decided to rewrite the DDN part in next version, eliminating these tables or any type of hard-coded configuration. > > > > +static CCU_DDN_DEFINE(slow_uart1_14p74, "slow_uart1_14p74", pll1_d16_153p6, > > > > + &uart_ddn_mask_info, slow_uart1_tbl, > > > > + MPMU_SUCCR, 0); > > > > +static CCU_DDN_DEFINE(slow_uart2_48, "slow_uart2_48", pll1_d4_614p4, > > > > + &uart_ddn_mask_info, slow_uart2_tbl, > > > > + MPMU_SUCCR_1, 0); > > > > + > > > > +static CCU_GATE_DEFINE(wdt_clk, "wdt_clk", CCU_PARENT_HW(pll1_d96_25p6), > > > > + MPMU_WDTPCR, > > > > + BIT(1), BIT(1), 0x0, > > > > + 0); > > > > + > > > > > > I couldn't find the "ripc_clk" on the clock tree diagram. It is > > > never used elsewhere, so I think this definition can go away. > > > > I'm not sure whether the ripc_clk doesn't exist or it's just missing in > > both datasheet and clock tree diagram. Will confirm with the vendor. > > > > > > +static CCU_GATE_DEFINE(ripc_clk, "ripc_clk", CCU_PARENT_NAME(vctcxo_24m), > > > > + MPMU_RIPCCR, > > > > + 0x3, 0x3, 0x0, > > > > + 0); > > > > + > > > > +static CCU_GATE_FACTOR_DEFINE(i2s_sysclk, "i2s_sysclk", CCU_PARENT_HW(pll1_d16_153p6), > > > > + MPMU_ISCCR, > > > > + BIT(31), BIT(31), 0x0, 50, 1, > > > > + 0); > > > > +static CCU_GATE_FACTOR_DEFINE(i2s_bclk, "i2s_bclk", CCU_PARENT_HW(i2s_sysclk), > > > > + MPMU_ISCCR, > > > > + BIT(29), BIT(29), 0x0, 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_clk", apb_parents, > > > > + MPMU_APBCSCR, > > > > + 0, 2, > > > > + 0); > > > > + > > > > > > The following clock is just called "wdt_clk" on the clock tree diagram. > > > > They're different things. The wdt_clk is defined a few lines earlier and > > acts as the function clock of watchdog. Instead, wdt_bus_clk is its bus > > clock. > > > > Most bus clocks in the clock tree diagram aren't listed explicitly, but > > represented as a gate besides the function clock. > > OK, this is good to know. Thank you. > > > > > > > +static CCU_GATE_DEFINE(wdt_bus_clk, "wdt_bus_clk", CCU_PARENT_HW(apb_clk), > > > > + MPMU_WDTPCR, > > > > + BIT(2), BIT(2), 0x0, > > > > + 0); > > > > I do find a typo here. BIT(0) should be used here instead of BIT(2), > > which is the reset bit for watchdog. > > > > > > +/* 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), > > > > +}; > > > > > > I might be misunderstanding this, but all of the 9 UART clocks below > > > share a common gate. > > > > AFAIK they don't share the common gate. Each uart comes with its own > > functional gate, bus gate and multiplexer. The diagram seems a little > > confusing: only one gate is drawn to avoid duplication. > > OK this is also good to know, thanks. > > > This applies for PWM as well. There're 10 different UART clock/reset > > registers, each contains a gate bit for functional clock and one for > > bus clock, confirming the idea[1]. > > > > > I *think* that gate should be represented as a distinct clock so that > > > it can be properly reference-counted. There are numerous cases of this. > > After I sent this I concluded the above statement was wrong. > > > > Also note there is no uart1 clock; I think the clock tree diagram > > > erroneously calls the first clock "uart1" (not uart0). > > > > The clocks are reordered to match our current devicetree, where the > > first uart is uart0 and uart1 is assigned to the one in RCPU (real-time > > CPU) region. > > I guess what I care about is that it is done in a way that avoids > any confusion. I believe what you describe makes sense. > > > > > +static CCU_MUX_GATE_DEFINE(uart0_clk, "uart0_clk", uart_clk_parents, > > > > + APBC_UART1_CLK_RST, > > > > + 4, 3, BIT(1), BIT(1), 0x0, > > > > + CLK_IS_CRITICAL); > > . . . > > > > Here we begin a bunch of definitions of "bus clocks". They are all > > > simply gates, and the clock tree diagram shows these paired with > > > another gated parent. > > > > Please note in the diagram, the clock beside the bus clock isn't the > > parent of the bus clock. Without special notes, all bus clocks in the > > APBC region take apb_clk as parent. All bus clocks in the APMU region > > take pmua_aclk as parent. > > OK, I think there might be a note at the top of the diagram > that suggests this, but I'm very happy to have you explain it > to me. > > > > This says to me that in order to use a "child clock" in this > > > situation, both the "regular gate" and the "bus gate" clock must be > > > defined, and a reference to it taken (with clk_get() or similar). > > > > No, functional clock and bus clock are completely irrelevant. They > > control different parts of a peripheral. Taking UART as example, you do > > need to take both function and bus clock for normal operation; but if > > the bus clock is disabled, the UART could continue to send data. Only > > the bus component won't work and the registers couldn't be accessed. > > The bus clocks sound like interconnects. But I'm not going to > even go there right now... > > > > Can you confirm this? > > > > + > > . . . > > > > The next two parent clocks are duplicates. It looks this way on the > > > clock tree diagram as well. Is this correct? Can you find out from > > > SpacemiT whether one of them is actually a different clock (like > > > pll2_d6 or something)? It makes no sense to have two multiplexed > > > parent clocks with the same source. > > > > Yes, will confirm it later. The register description[2] suggests it's > > wrong (there aren't two configuration for MIPI_BIT_CLK_SEL resulting in > > the same frequency). > > > > > > + CCU_PARENT_HW(pll2_d8), > > > > + CCU_PARENT_HW(pll2_d8), > > > > +}; > > OK thank you. This has been confirmed in the earlier message as well, the duplicated parents ARE wrong. Thanks for pointing this out. > > > > +static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, "dpu_bit_clk", dpubit_parents, > > > > + APMU_LCD_CLK_RES_CTRL1, > > > > + 17, 3, BIT(31), > > > > + 20, 3, BIT(16), BIT(16), 0x0, > > > > + 0); > > . . . > > > > > +static CCU_DIV_FC_MUX_GATE_DEFINE(sdh1_clk, "sdh1_clk", sdh01_parents, > > > > + APMU_SDH1_CLK_RES_CTRL, > > > > + 8, 3, BIT(11), > > > > + 5, 3, BIT(4), BIT(4), 0x0, > > > > + 0); > > > > > > This is strange too. sdh2_parents is identical to sdh01_parents. > > > > No, it isn't. pll2_d5 is a parent for sdh0/1; for sdh2, pll1_d3_819p2 > > replaces it. > > I see that now in the code, but not in the diagram. > I guess I'll assume the code is right. > > > > All of the bits used are identical for the three control registers. > > > The clock tree diagram shows all three of these sdhx clocks sharing > > > a single parent mux. Why is sdh2_parents defined separately? > > > > I don't think the mux is shared. It should be the same case for > > UART/PWM clocks, as described earlier. > > I'm sure you're right. > > > > > +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), > > > > +}; > > . . . > > > > You could reduce some duplication here using a simple > > > macro for defining the address of the clk_hw structure: > > > > > > #define CLK_HW(x) &x.common.hw > > > > > > (Add parentheses if you like.) It saves 3 characters for > > > each clock... > > > > Looks good to me, but > > Don't bother for now. It can be fixed easily later if > it's important. Thanks. > > > > +static struct clk_hw_onecell_data k1_ccu_apbs_clks = { > > > > + .hws = { > > > > + [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, > > > > + > > > > + }, > > > > + .num = CLK_APBS_NUM, > > > > > > It sure would be nice to be able to use ARRAY_SIZE() here but > > > alas I don't think it's possible here. > > > > CLK_*_NUM will be removed in the next revision, as it's actually not a > > binding. I'm looking for a better way to represent these clocks. > > > > Maybe represent the clocks with (ID, struct clk_hw *) pairs and > > generate struct clk_hw_onecell_data dynamically during probe. > > Yes I think I have a suggestion but I'll hold off until I send > out another message in which I suggest something different. > > > > > +}; > > . . . > > > > The only time the "PLL lock" is needed is with PLL clocks. Can you > > > encode this flag, or perhaps the address of the lock register, > > > into the ccu_pll structure instead somehow? > > > > > > If so you wouldn't need this spacemit_ccu_data structure, and could > > > simply use the clk_hw_onecell_data structure in its place. > > > > We could scan the clk_hw_onecell_data array before the probing, but I > > don't think it's worth. > > > > As the raw clk_hw_onecell_data is likely to be removed in the next > > revision, I'll try to avoid the flag during refactoring. > > I think you'll be able to. > > > > > +struct spacemit_ccu_data { > > > > + struct clk_hw_onecell_data *hw_clks; > > > > + bool need_pll_lock; > > > > +}; > > . . . > > > > The next two pointers are regmap pointers. Could the > > > name of the fields suggest that? (They sound like raw > > > memory addresses to me.) > > > > Do "common.regmap" and "common.lock_regmap" sound better for you? > > Yes I think that's an improvement. > > > > > + common->base = priv->base; > > > > + common->lock_base = priv->lock_base; > > . . . > > > > > + ret = devm_clk_hw_register(dev, hw); > > > > + if (ret) { > > > > + dev_err(dev, "Cannot register clock %d - %s\n", > > > > + i, name); > > > > > > Just use hw->init->name here instead of name. > > > > Will change in the next version. > > . . . > > > > You allocate the priv structure, but never free it (a bug). > > > It only holds 3 values; just pass them as arguments when > > > registering the CCU, as suggested above. > > > > The allocation is done through a devres variant, it should be released > > automatically. Please correct me if I'm wrong. > > You're right. But my other point about priv being unnecessary > (just pass the three values as arguments) remains. Makes sense to me, I'll drop the structure. > > > > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > > > > + if (!priv) > > > > + return -ENOMEM; > > . . . > > > > End of "ccu-k1.c"!!! > > > > It's really a lot of lines lol > > > > > > diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h > > > > new file mode 100644 > > > > index 000000000000..242461ee592f > > > > --- /dev/null > > > > +++ b/drivers/clk/spacemit/ccu_common.h > > > > @@ -0,0 +1,51 @@ > > . . . > > > > Although I think the following macros are nice and concise, they > > > obscure the fact that they're essentially renaming existing > > > regmap operations. They don't really add much value; the > > > regmap functions are pretty clear. > > > > They're just for saving some characters, > > > > regmap_read(common->base, common->reg_ctrl, &tmp) > > > > is much longer than > > > > ccu_read(common, ctrl, &tmp) > > > > Of course it could be changed, if you're still strongly against it, > > It's OK for now, but I might make the same suggestion in a future > version of the series. Okay, then will keep it as it, at least for now. > > > Although regmap_read() returns a value, you never check for an > > > error when calling ccu_read(). > > > > I don't expect that access to a MMIO regmap could fail. There shouldn't > > be any cases of failure, so I don't check it. > > > > > > +#define ccu_read(reg, c, val) regmap_read((c)->base, (c)->reg_##reg, val) > > It should never return an error. But what if it did? I'm > mainly thinking about the very first attempt to read from > the I/O space. It's probably OK; lots of existing code > ignores any return value from regmap_read. > > . . . > > > > > diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c > > > > new file mode 100644 > > > > index 000000000000..1df555888ecb > > > > --- /dev/null > > > > +++ b/drivers/clk/spacemit/ccu_ddn.c > > . . . > > > > The next three functions are never used and can be removed. > > > > > > The reason is that the only time they'd be used is when > > > CCU_DDN_GATE_DEFINE() (defined in "ccu_ddn.h") is called, > > > but it never is. So you can get rid of CCU_DDN_GATE_DEFINE(), > > > and once you do that there's no need for ccu_ddn_disable(), > > > ccu_ddn_enable(), and ccu_ddn_is_enabled(). Furthermore, > > > these three functions are the only place the "reg_sel" > > > field in the ccu_common structure is used, and without > > > these functions, that field serves no purpose. So that > > > field can go away, and there's no need to provide a value > > > for initializing it in all those clock definition macros. > > > > When cleaning up the driver from the vendor source, I tried to keep most > > clock types included since future SpacemiT SoCs made may make use of > > them. It seems too farsighted as this patch is already quite large. > > > > I'll remove the unused clock types as well as the reg_sel field, thanks. > > This is good practice. Don't add code that you expect > to use at a future date. Keep that out, and add it in > *when* you need it. Otherwise it's basically dead code. > > . . . > > > > I'm going to say more about this below, but I think the > > > factor can be removed and hard-coded here. > > > > Alright, we could hardcode it for now to make the patch as simple as > > possible. If different factors are used in future SoCs, it isn't hard > > to add it back later. > > Exactly. > > > > > + rate = (*prate * params->tbl[i].den) / > > > > + (params->tbl[i].num * params->info->factor); > > > > > . . . > > > > Here is a general comment that applies to a bunch of places in > > > this code. You can concisely define a field with a single > > > mask value. That means there is no need to define both a mask > > > (or a field width) and a shift. What you need is defined in > > > <linux/bitfield.h>. There you will find a set of macros > > > like FIELD_GET() and FIELD_PREP(), or alternatively a set > > > of inline functions like u32_get_bits() and u32_encode_bits(). > > > > I think both macros and inline functions in bitfield.h work with only > > constant masks. With non-constant masks, some checks cannot be optimized > > out, resulting in compilation errors. > > Yes, this was true some time back and I suppose it still is. > For some reason I thought there might have been a way to use > non-constant masks. Anyway, I still think something similar > to u32_get_bits() or FIELD_GET() is nicely encapsulated and > less error-prone than "manually" shifting and masking. > > Don't worry about it for now, but again I might suggest it > again. > > > > > + unsigned int num_mask; > > > > + unsigned int den_mask; > > > > + unsigned int num_shift; > > > > + unsigned int den_shift; > > > > +}; > > . . . > > > > > diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c > > > > new file mode 100644 > > > > index 000000000000..b46eeb59faea > > > > --- /dev/null > > > > +++ b/drivers/clk/spacemit/ccu_mix.c > > . . . > > > > > +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; > > > > + > > > > + if (mux->table) > > > > > > You should verify that the index is less than the > > > number of entries in the table before you blindly > > > dereference it here. > > > > The desired index is found by searching in clk_fetch_parent_index(), > > thus is always be valid, isn't it? > > > > However, this is a dead codepath: no clock hardware comes with a valid > > table. The whole if could be removed for simplication. > > That's even better. > > > > > + index = mux->table[index]; > > . . . > > > > > + u32 val_enable; > > > > + u32 val_disable; > > > > + u32 flags; > > > > > > When you have a flags field like this, it's helpful to add a comment > > > that indicates what values it can take on. I believe in all cases > > > like this, the flags values are standard clock framework flags. > > > > Yes. > > > > > So what I mean is do something like this: > > > > > > u32 flags; /* E.g., CLK_IGNORE_UNUSED */ > > > > Do you think > > > > u32 flags; /* standard CCF flags */ > > > > ls more descriptive? > > No, but your way is OK. Thanks. > > > > > > +}; > > . . . > > > > > +#define CCU_GATE_DEFINE(_struct, _name, _parent, _reg, _gate_mask, \ > > > > + _val_enable, _val_disable, _flags) \ > > > > > > There is one case where the _flags value is CLK_IGNORE_UNUSED, and > > > one case where it is CLK_IS_CRITICAL. > > > > The UART0 (booting UART) and all core clocks are marked as > > CLK_IS_CRTICAL, not just one case. > > > > > All others pass 0. Given > > > that, you could define two macros and remove the 0 argument from > > > most of the callers: > > > > I'd like to always keep the flags field, since keeping the clock > > definitions intact and in the same format helps reading. There're a > > bunch of clocks. > > OK. > > . . . > > > > I'd put the "2" somewhere else in the name of this. It suggests > > > "divide by two" but it's really a "second DIV_FC_MUX_GATE" macro. > > > > Is DIV_SPLIT_FC_MUX_GATE a better name? The difference between this and > > DIV_FC_MUX_GATE is that the fc bit locates in a different register. > > Either is fine. My point was about "DIV2" suggesting "divide > by two". > > . . . > > > > > diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c > > > > new file mode 100644 > > > > index 000000000000..81b929ca1c5c > > > > --- /dev/null > > > > +++ b/drivers/clk/spacemit/ccu_pll.c > > . . . > > > > > + unsigned int i; > > > > + > > > > + for (i = 0; i < params->tbl_size; i++) { > > > > + if (params->rate_tbl[i].rate <= rate) { > > > > + if (max_rate < params->rate_tbl[i].rate) > > > > + max_rate = params->rate_tbl[i].rate; > > > > + } > > > > + } > > > > + > > > > > > I don't like that you're passing a compile-time defined > > > frequency (which might not match reality). > > > > We could make the rate_tbl sorted, which should improve both set_rate > > and round_rate a lot. > > That might be nice, but it's not strictly necessary. But if you > don't do that, fix the loop above. > > > > > + return MAX(max_rate, PLL_MIN_FREQ); > > > > +} > > . . . > > That's the end of my response. > > Now on to my bigger proposal. Do *not* delay posting the > next version of your series based on my next message. Which one does "next message" refer to? Is it the one about dt-binding designing, or should I wait for another message? > Just send out an update that addresses all of the feedback > you have received so far. If my proposal makes sense we can > talk about how to modify that next version to incorporate it. Okay. > Thanks. > > -Alex Best regards, Haylen Chu ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-02-16 11:34 ` Haylen Chu 2025-02-21 21:10 ` Alex Elder @ 2025-02-22 9:40 ` Haylen Chu 1 sibling, 0 replies; 31+ messages in thread From: Haylen Chu @ 2025-02-22 9:40 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, Inochi Amaoto, Chen Wang, Guodong Xu Hi Alex, Before answering the reply, I'd like to share some information on these unconfirmed questions. On Sun, Feb 16, 2025 at 11:34:06AM +0000, Haylen Chu wrote: > On Thu, Feb 13, 2025 at 10:04:10PM -0600, Alex Elder wrote: > > On 1/3/25 3:56 PM, Haylen Chu wrote: > > > diff --git a/drivers/clk/spacemit/ccu-k1.c b/drivers/clk/spacemit/ccu-k1.c > > > new file mode 100644 > > > index 000000000000..6fb0a12ec261 > > > --- /dev/null > > > +++ b/drivers/clk/spacemit/ccu-k1.c ... > The next clock is weird, and it's the only one of its kind. It is not > > represented in the clock tree diagram. It is a "factor 1" clock (so it > > just passes the parent's rate through), and has no gate. Do you know > > why it's defined? It is used only as one of the MPMU parent clocks. > > Why isn't just the pll1_d7 clock used in its place? > > It is represented in the diagram. The photo version of the diagram seems > hard to search so I will ask the vendor to publish a PDF version if > possible. > > As the definition involves no hardware bits, I guess it's actually an > alias listed to keep the tree structure in similar form. Will confirm > this with the vendor. Yes, it's confirmed as a placeholder. > > > > +static CCU_FACTOR_DEFINE(pll1_d7_351p08, "pll1_d7_351p08", CCU_PARENT_HW(pll1_d7), > > > + 1, 1); > > > + > > > +static CCU_GATE_DEFINE(pll1_d6_409p6, "pll1_d6_409p6", CCU_PARENT_HW(pll1_d6), > > > + MPMU_ACGR, > > > + BIT(0), BIT(0), 0, 0); > > > +static CCU_GATE_FACTOR_DEFINE(pll1_d12_204p8, "pll1_d12_204p8", CCU_PARENT_HW(pll1_d6), > > > + MPMU_ACGR, > > > + BIT(5), BIT(5), 0, 2, 1, 0); > > > + > > > +static CCU_GATE_DEFINE(pll1_d5_491p52, "pll1_d5_491p52", CCU_PARENT_HW(pll1_d5), > > > + MPMU_ACGR, > > > + BIT(21), BIT(21), 0, 0); > > > +static CCU_GATE_FACTOR_DEFINE(pll1_d10_245p76, "pll1_d10_245p76", CCU_PARENT_HW(pll1_d5), > > > + MPMU_ACGR, > > > + BIT(18), BIT(18), 0, 2, 1, 0); > > > + > > > +static CCU_GATE_DEFINE(pll1_d4_614p4, "pll1_d4_614p4", CCU_PARENT_HW(pll1_d4), > > > + MPMU_ACGR, > > > + BIT(15), BIT(15), 0, 0); > > > +static CCU_GATE_FACTOR_DEFINE(pll1_d52_47p26, "pll1_d52_47p26", CCU_PARENT_HW(pll1_d4), > > > + MPMU_ACGR, > > > + BIT(10), BIT(10), 0, 13, 1, 0); > > > +static CCU_GATE_FACTOR_DEFINE(pll1_d78_31p5, "pll1_d78_31p5", CCU_PARENT_HW(pll1_d4), > > > + MPMU_ACGR, > > > + BIT(6), BIT(6), 0, 39, 2, 0); > > > + > > > +static CCU_GATE_DEFINE(pll1_d3_819p2, "pll1_d3_819p2", CCU_PARENT_HW(pll1_d3), > > > + MPMU_ACGR, > > > + BIT(14), BIT(14), 0, 0); > > > + > > > +static CCU_GATE_DEFINE(pll1_d2_1228p8, "pll1_d2_1228p8", CCU_PARENT_HW(pll1_d2), > > > + MPMU_ACGR, > > > + BIT(16), BIT(16), 0, 0); ... > > I couldn't find the "ripc_clk" on the clock tree diagram. It is > > never used elsewhere, so I think this definition can go away. > > I'm not sure whether the ripc_clk doesn't exist or it's just missing in > both datasheet and clock tree diagram. Will confirm with the vendor. It's just missing in the datasheet and clock tree diagram and now they have been completed[1]. > > > +static CCU_GATE_DEFINE(ripc_clk, "ripc_clk", CCU_PARENT_NAME(vctcxo_24m), > > > + MPMU_RIPCCR, > > > + 0x3, 0x3, 0x0, > > > + 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), > > > > The next two parent clocks are duplicates. It looks this way on the > > clock tree diagram as well. Is this correct? Can you find out from > > SpacemiT whether one of them is actually a different clock (like > > pll2_d6 or something)? It makes no sense to have two multiplexed > > parent clocks with the same source. > > Yes, will confirm it later. The register description[2] suggests it's > wrong (there aren't two configuration for MIPI_BIT_CLK_SEL resulting in > the same frequency). The clock tree diagram (and the vendor driver) were wrong. The 7th. parent should be pll2_d7 (427MHz * 7 is roughly 3GHz, which is PLL2's frequency). The diagram has been corrected. > > > + CCU_PARENT_HW(pll2_d8), > > > + CCU_PARENT_HW(pll2_d8), > > > +}; > > > +static CCU_DIV_FC_MUX_GATE_DEFINE(dpu_bit_clk, "dpu_bit_clk", dpubit_parents, > > > + APMU_LCD_CLK_RES_CTRL1, > > > + 17, 3, BIT(31), > > > + 20, 3, BIT(16), BIT(16), 0x0, > > > + 0); > > > + ... > > > diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c > > > new file mode 100644 > > > index 000000000000..1df555888ecb > > > --- /dev/null > > > +++ b/drivers/clk/spacemit/ccu_ddn.c > > > @@ -0,0 +1,140 @@ > > > +// 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 "ccu_ddn.h" > > > > What does "DDN" stand for? > > I'm not sure, the name is kept from the vendor driver. I could change it > to a more descriptive name, like "fraction-factor". It's abbreviated from "Divider Denominator Numerator", confirmed by the vendor. Quite weird a name. I'll make the abbreviation and corresponding explanation more clear in the next revision. Thanks, Haylen Chu [1]: https://developer.spacemit.com/documentation?token=LCrKwWDasiJuROkVNusc2pWTnEb#part208 ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-02-14 4:04 ` Alex Elder 2025-02-16 11:34 ` Haylen Chu @ 2025-03-03 9:41 ` Haylen Chu 2025-03-03 14:10 ` Alex Elder 1 sibling, 1 reply; 31+ messages in thread From: Haylen Chu @ 2025-03-03 9:41 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, Inochi Amaoto, Chen Wang, Guodong Xu On Thu, Feb 13, 2025 at 10:04:10PM -0600, Alex Elder wrote: > On 1/3/25 3:56 PM, Haylen Chu wrote: > > The clock tree of K1 SoC contains three main types of clock hardware > > (PLL/DDN/MIX) and is managed by several independent controllers in > > different SoC parts (APBC, APBS and etc.), thus different compatible > > strings are added to distinguish them. > > > > Some controllers may share IO region with reset controller and other low > > speed peripherals like watchdog, so all register operations are done > > through regmap to avoid competition. > > > > Signed-off-by: Haylen Chu <heylenay@4d2.org> > > This is a really big patch (over 3000 lines), and a fairly large > amount of code to review. But I've given it a really thorough > read and I have a *lot* of review comments for you to consider. > > First, a few top-level comments. > - This driver is very comprehensive. It represents essentially > *all* of the clocks in the tree diagram shown here: > https://developer.spacemit.com/resource/file/images?fileName=DkWGb4ed7oAziVxE6PIcbjTLnpd.png > (I can tell you what's missing but I don't think it matters.) > - In almost all cases, the names of the clocks match the names > shown in that diagram, which is very helpful. > - All of the clocks are implemented using "custom" clock > implementations. I'm fairly certain that almost all of > them can use standard clock framework types instead > (fixed-rate, fixed-factor, fractional-divider, mux, and > composite). But for now I think there are other things > more important to improve. > - A great deal of my commentary below is simply saying that the > code is more complex than necessary. Some simple (though > widespread) refactoring would improve things a lot. And > some of the definitions can be done without having to > specify nearly so many values. > - Much of what might be considered generality in the > implementation actually isn't needed, because it isn't used. > This is especially true given that there are essentially no > clocks left unspecified for the K1 SoC. > - Once the refactoring I suggest has been done, I expect > that more opportunities for simplification and cleanup will > become obvious; we'll see. > - I suggest these changes because the resulting simplicity > will make the code much more understandable and maintainable > in the long term. And if it's simpler to understand, it > should be easier for a maintainer to accept. > > I'm not going to comment on the things related to Device Tree > that have already been mentioned, nor on the Makefile or Kconfig, > etc. I'm focusing just on the code. > > > --- > > drivers/clk/Kconfig | 1 + > > drivers/clk/Makefile | 1 + > > drivers/clk/spacemit/Kconfig | 20 + > > drivers/clk/spacemit/Makefile | 5 + > > drivers/clk/spacemit/ccu-k1.c | 1747 +++++++++++++++++++++++++++++ > > drivers/clk/spacemit/ccu_common.h | 51 + > > drivers/clk/spacemit/ccu_ddn.c | 140 +++ > > drivers/clk/spacemit/ccu_ddn.h | 84 ++ > > drivers/clk/spacemit/ccu_mix.c | 304 +++++ > > drivers/clk/spacemit/ccu_mix.h | 309 +++++ > > drivers/clk/spacemit/ccu_pll.c | 189 ++++ > > drivers/clk/spacemit/ccu_pll.h | 80 ++ > > 12 files changed, 2931 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..6fb0a12ec261 > > --- /dev/null > > +++ b/drivers/clk/spacemit/ccu-k1.c ... > The next set of clocks differ from essentially all others, in that > they don't encode their frequency in the name. I.e., I would expect > the first one to be named pll1_d2_1228p8. I found this change may not be possible: with the frequency appended, their names conflict with another set of MPMU gates. > > > +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(1), BIT(1), 0, 2, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(2), BIT(2), 0, 3, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(3), BIT(3), 0, 4, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(4), BIT(4), 0, 5, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(5), BIT(5), 0, 6, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(6), BIT(6), 0, 7, 1, 0); > > +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), > > + APB_SPARE2_REG, > > + BIT(7), BIT(7), 0, 8, 1, 0); > > + ... > > +/* MPMU clocks start */ ... > > +static CCU_GATE_DEFINE(pll1_d3_819p2, "pll1_d3_819p2", CCU_PARENT_HW(pll1_d3), > > + MPMU_ACGR, > > + BIT(14), BIT(14), 0, 0); > > + > > +static CCU_GATE_DEFINE(pll1_d2_1228p8, "pll1_d2_1228p8", CCU_PARENT_HW(pll1_d2), > > + MPMU_ACGR, > > + BIT(16), BIT(16), 0, 0); Here're the conflicts. Although they don't happen on all the clocks, I prefer to keep the clock names as is for now to keep the consistency. Thanks, Haylen Chu ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC 2025-03-03 9:41 ` Haylen Chu @ 2025-03-03 14:10 ` Alex Elder 0 siblings, 0 replies; 31+ messages in thread From: Alex Elder @ 2025-03-03 14:10 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, Inochi Amaoto, Chen Wang, Guodong Xu On 3/3/25 3:41 AM, Haylen Chu wrote: > On Thu, Feb 13, 2025 at 10:04:10PM -0600, Alex Elder wrote: >> On 1/3/25 3:56 PM, Haylen Chu wrote: >>> The clock tree of K1 SoC contains three main types of clock hardware >>> (PLL/DDN/MIX) and is managed by several independent controllers in >>> different SoC parts (APBC, APBS and etc.), thus different compatible >>> strings are added to distinguish them. >>> >>> Some controllers may share IO region with reset controller and other low >>> speed peripherals like watchdog, so all register operations are done >>> through regmap to avoid competition. >>> >>> Signed-off-by: Haylen Chu <heylenay@4d2.org> >> >> This is a really big patch (over 3000 lines), and a fairly large >> amount of code to review. But I've given it a really thorough >> read and I have a *lot* of review comments for you to consider. >> >> First, a few top-level comments. >> - This driver is very comprehensive. It represents essentially >> *all* of the clocks in the tree diagram shown here: >> https://developer.spacemit.com/resource/file/images?fileName=DkWGb4ed7oAziVxE6PIcbjTLnpd.png >> (I can tell you what's missing but I don't think it matters.) >> - In almost all cases, the names of the clocks match the names >> shown in that diagram, which is very helpful. >> - All of the clocks are implemented using "custom" clock >> implementations. I'm fairly certain that almost all of >> them can use standard clock framework types instead >> (fixed-rate, fixed-factor, fractional-divider, mux, and >> composite). But for now I think there are other things >> more important to improve. >> - A great deal of my commentary below is simply saying that the >> code is more complex than necessary. Some simple (though >> widespread) refactoring would improve things a lot. And >> some of the definitions can be done without having to >> specify nearly so many values. >> - Much of what might be considered generality in the >> implementation actually isn't needed, because it isn't used. >> This is especially true given that there are essentially no >> clocks left unspecified for the K1 SoC. >> - Once the refactoring I suggest has been done, I expect >> that more opportunities for simplification and cleanup will >> become obvious; we'll see. >> - I suggest these changes because the resulting simplicity >> will make the code much more understandable and maintainable >> in the long term. And if it's simpler to understand, it >> should be easier for a maintainer to accept. >> >> I'm not going to comment on the things related to Device Tree >> that have already been mentioned, nor on the Makefile or Kconfig, >> etc. I'm focusing just on the code. >> >>> --- >>> drivers/clk/Kconfig | 1 + >>> drivers/clk/Makefile | 1 + >>> drivers/clk/spacemit/Kconfig | 20 + >>> drivers/clk/spacemit/Makefile | 5 + >>> drivers/clk/spacemit/ccu-k1.c | 1747 +++++++++++++++++++++++++++++ >>> drivers/clk/spacemit/ccu_common.h | 51 + >>> drivers/clk/spacemit/ccu_ddn.c | 140 +++ >>> drivers/clk/spacemit/ccu_ddn.h | 84 ++ >>> drivers/clk/spacemit/ccu_mix.c | 304 +++++ >>> drivers/clk/spacemit/ccu_mix.h | 309 +++++ >>> drivers/clk/spacemit/ccu_pll.c | 189 ++++ >>> drivers/clk/spacemit/ccu_pll.h | 80 ++ >>> 12 files changed, 2931 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..6fb0a12ec261 >>> --- /dev/null >>> +++ b/drivers/clk/spacemit/ccu-k1.c > > ... > >> The next set of clocks differ from essentially all others, in that >> they don't encode their frequency in the name. I.e., I would expect >> the first one to be named pll1_d2_1228p8. > > I found this change may not be possible: with the frequency appended, > their names conflict with another set of MPMU gates. OK, that's fine, and perhaps is why it was done this way. Thanks for checking. I look forward to the next version of the series. -Alex > >> >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d2, "pll1_d2", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(1), BIT(1), 0, 2, 1, 0); >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d3, "pll1_d3", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(2), BIT(2), 0, 3, 1, 0); >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d4, "pll1_d4", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(3), BIT(3), 0, 4, 1, 0); >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d5, "pll1_d5", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(4), BIT(4), 0, 5, 1, 0); >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d6, "pll1_d6", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(5), BIT(5), 0, 6, 1, 0); >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d7, "pll1_d7", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(6), BIT(6), 0, 7, 1, 0); >>> +static CCU_GATE_FACTOR_DEFINE(pll1_d8, "pll1_d8", CCU_PARENT_HW(pll1), >>> + APB_SPARE2_REG, >>> + BIT(7), BIT(7), 0, 8, 1, 0); >>> + > > ... > >>> +/* MPMU clocks start */ > > ... > >>> +static CCU_GATE_DEFINE(pll1_d3_819p2, "pll1_d3_819p2", CCU_PARENT_HW(pll1_d3), >>> + MPMU_ACGR, >>> + BIT(14), BIT(14), 0, 0); >>> + >>> +static CCU_GATE_DEFINE(pll1_d2_1228p8, "pll1_d2_1228p8", CCU_PARENT_HW(pll1_d2), >>> + MPMU_ACGR, >>> + BIT(16), BIT(16), 0, 0); > > Here're the conflicts. > > Although they don't happen on all the clocks, I prefer to keep the clock > names as is for now to keep the consistency. > > Thanks, > Haylen Chu ^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v4 4/4] riscv: dts: spacemit: Add clock controller for K1 2025-01-03 21:56 [PATCH v4 0/4] Add clock controller support for SpacemiT K1 Haylen Chu ` (2 preceding siblings ...) 2025-01-03 21:56 ` [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu @ 2025-01-03 21:56 ` Haylen Chu 3 siblings, 0 replies; 31+ messages in thread From: Haylen Chu @ 2025-01-03 21:56 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, Inochi Amaoto, Chen Wang, Jisheng Zhang, Meng Zhang, Haylen Chu Add clock controllers for APBC, APBS, APMU and MPMU regions along with system controllers which they belong to. Signed-off-by: Haylen Chu <heylenay@4d2.org> --- arch/riscv/boot/dts/spacemit/k1.dtsi | 97 ++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/arch/riscv/boot/dts/spacemit/k1.dtsi b/arch/riscv/boot/dts/spacemit/k1.dtsi index 0777bf9e0118..a2cd141f9177 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>; @@ -318,6 +320,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>; @@ -326,6 +362,21 @@ soc { dma-noncoherent; ranges; + syscon_apbc: system-control@d4015000 { + compatible = "spacemit,k1-apbc-syscon", "syscon", + "simple-mfd"; + reg = <0x0 0xd4015000 0x0 0x1000>; + + clk_apbc: clock-controller { + compatible = "spacemit,k1-ccu-apbc"; + clocks = <&osc_32k>, <&vctcxo_1m>, + <&vctcxo_3m>, <&vctcxo_24m>; + clock-names = "osc", "vctcxo_1m", + "vctcxo_3m", "vctcxo_24m"; + #clock-cells = <1>; + }; + }; + uart0: serial@d4017000 { compatible = "spacemit,k1-uart", "intel,xscale-uart"; reg = <0x0 0xd4017000 0x0 0x100>; @@ -416,6 +467,52 @@ uart9: serial@d4017800 { status = "disabled"; }; + syscon_mpmu: system-control@d4050000 { + compatible = "spacemit,k1-mpmu-syscon", "syscon", + "simple-mfd"; + reg = <0x0 0xd4050000 0x0 0x209c>; + + clk_mpmu: clock-controller { + compatible = "spacemit,k1-ccu-mpmu"; + clocks = <&osc_32k>, <&vctcxo_1m>, + <&vctcxo_3m>, <&vctcxo_24m>; + clock-names = "osc", "vctcxo_1m", + "vctcxo_3m", "vctcxo_24m"; + #clock-cells = <1>; + }; + }; + + syscon_apbs: system-control@d4090000 { + compatible = "spacemit,k1-apbs-syscon", "syscon", + "simple-mfd"; + reg = <0x0 0xd4090000 0x0 0x1000>; + + clk_apbs: clock-controller { + compatible = "spacemit,k1-ccu-apbs"; + clocks = <&osc_32k>, <&vctcxo_1m>, + <&vctcxo_3m>, <&vctcxo_24m>; + clock-names = "osc", "vctcxo_1m", + "vctcxo_3m", "vctcxo_24m"; + spacemit,mpmu = <&syscon_mpmu>; + #clock-cells = <1>; + }; + }; + + syscon_apmu: system-control@d4282800 { + compatible = "spacemit,k1-apmu-syscon", "syscon", + "simple-mfd"; + reg = <0x0 0xd4282800 0x0 0x400>; + + clk_apmu: clock-controller { + compatible = "spacemit,k1-ccu-apmu"; + clocks = <&osc_32k>, <&vctcxo_1m>, + <&vctcxo_3m>, <&vctcxo_24m>; + clock-names = "osc", "vctcxo_1m", + "vctcxo_3m", "vctcxo_24m"; + #clock-cells = <1>; + }; + }; + plic: interrupt-controller@e0000000 { compatible = "spacemit,k1-plic", "sifive,plic-1.0.0"; reg = <0x0 0xe0000000 0x0 0x4000000>; -- 2.47.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
end of thread, other threads:[~2025-03-03 14:10 UTC | newest] Thread overview: 31+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-01-03 21:56 [PATCH v4 0/4] Add clock controller support for SpacemiT K1 Haylen Chu 2025-01-03 21:56 ` [PATCH v4 1/4] dt-bindings: clock: spacemit: Add clock controllers of Spacemit K1 SoC Haylen Chu 2025-01-04 9:58 ` Krzysztof Kozlowski 2025-01-15 7:29 ` Haylen Chu 2025-01-03 21:56 ` [PATCH v4 2/4] dt-bindings: soc: spacemit: Add spacemit,k1-syscon Haylen Chu 2025-01-04 10:07 ` Krzysztof Kozlowski 2025-02-11 5:15 ` Haylen Chu 2025-02-11 8:03 ` Krzysztof Kozlowski 2025-02-13 11:14 ` Haylen Chu 2025-02-13 18:07 ` Krzysztof Kozlowski 2025-02-15 8:41 ` Haylen Chu 2025-02-21 23:40 ` Alex Elder 2025-02-22 9:59 ` Krzysztof Kozlowski 2025-02-22 10:48 ` Haylen Chu 2025-02-22 11:50 ` Krzysztof Kozlowski 2025-02-24 10:17 ` Haylen Chu 2025-02-25 8:12 ` Krzysztof Kozlowski 2025-02-25 21:14 ` Alex Elder 2025-02-22 9:52 ` Krzysztof Kozlowski 2025-02-22 11:36 ` Haylen Chu 2025-01-03 21:56 ` [PATCH v4 3/4] clk: spacemit: Add clock support for Spacemit K1 SoC Haylen Chu 2025-01-16 5:25 ` Samuel Holland 2025-01-21 17:01 ` Haylen Chu 2025-02-14 4:04 ` Alex Elder 2025-02-16 11:34 ` Haylen Chu 2025-02-21 21:10 ` Alex Elder 2025-02-22 9:57 ` Haylen Chu 2025-02-22 9:40 ` Haylen Chu 2025-03-03 9:41 ` Haylen Chu 2025-03-03 14:10 ` Alex Elder 2025-01-03 21:56 ` [PATCH v4 4/4] riscv: dts: spacemit: Add clock controller for K1 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).