* [PATCH 0/4] clk: add Siflower SF21 topcrm support
@ 2026-05-17 14:12 Chuanhong Guo
2026-05-17 14:12 ` [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support Chuanhong Guo
` (3 more replies)
0 siblings, 4 replies; 22+ messages in thread
From: Chuanhong Guo @ 2026-05-17 14:12 UTC (permalink / raw)
To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Philipp Zabel
Cc: linux-riscv, linux-kernel, linux-clk, devicetree, Chuanhong Guo
Siflower SF21A6826 and SF21H8898 are RISC-V chips with quad-core
T-Head C908 for home routers and gateways.
This series adds the initial RISC-V Kconfig entry for Siflower SoCs and
support for the toplevel clock and reset module on Siflower SF21 socs.
Signed-off-by: Chuanhong Guo <gch981213@gmail.com>
---
Chuanhong Guo (4):
riscv: add Siflower RISC-V SoC family Kconfig support
dt-bindings: clock: add binding header for sf21-topcrm
dt-bindings: clock: add doc for Siflower sf21-topcrm
clk: add support for siflower sf21-topcrm
.../bindings/clock/siflower,sf21-topcrm.yaml | 69 ++
arch/riscv/Kconfig.socs | 7 +
drivers/clk/Kconfig | 1 +
drivers/clk/Makefile | 1 +
drivers/clk/siflower/Kconfig | 22 +
drivers/clk/siflower/Makefile | 1 +
drivers/clk/siflower/clk-sf21-topcrm.c | 1053 ++++++++++++++++++++
include/dt-bindings/clock/siflower,sf21-topcrm.h | 63 ++
8 files changed, 1217 insertions(+)
---
base-commit: e98d21c170b01ddef366f023bbfcf6b31509fa83
change-id: 20260515-sf21-topcrm-1856c05b6138
Best regards,
--
Chuanhong Guo <gch981213@gmail.com>
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support 2026-05-17 14:12 [PATCH 0/4] clk: add Siflower SF21 topcrm support Chuanhong Guo @ 2026-05-17 14:12 ` Chuanhong Guo 2026-05-17 14:31 ` sashiko-bot 2026-05-17 14:12 ` [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm Chuanhong Guo ` (2 subsequent siblings) 3 siblings, 1 reply; 22+ messages in thread From: Chuanhong Guo @ 2026-05-17 14:12 UTC (permalink / raw) To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel Cc: linux-riscv, linux-kernel, linux-clk, devicetree, Chuanhong Guo Siflower RISC-V SoCs, including SF21A6826 and SF21H8898, are RISC-V chips with T-Head C908 cores for home routers and gateways. Add a Kconfig entry named ARCH_SIFLOWER for them. Notably these chips uses ARM PL011 for UART. ARM_AMBA is selected for its driver. Signed-off-by: Chuanhong Guo <gch981213@gmail.com> --- arch/riscv/Kconfig.socs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index c174ac0ec46b..9996591cd9db 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -37,6 +37,13 @@ config ARCH_SIFIVE help This enables support for SiFive SoC platform hardware. +config ARCH_SIFLOWER + bool "Siflower RISC-V SoCs" + select ARM_AMBA if TTY + select ERRATA_THEAD + help + This enables support for Siflower RISC-V SoC platform hardware. + config ARCH_SOPHGO bool "Sophgo SoCs" help -- 2.54.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support 2026-05-17 14:12 ` [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support Chuanhong Guo @ 2026-05-17 14:31 ` sashiko-bot 2026-05-17 20:46 ` Conor Dooley 0 siblings, 1 reply; 22+ messages in thread From: sashiko-bot @ 2026-05-17 14:31 UTC (permalink / raw) To: Chuanhong Guo; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Conditionally selecting the `ARM_AMBA` hardware bus based on the `TTY` software subsystem. -- commit 49824e33ae15603df7f899c7df0a4ab9b5f20eed Author: Chuanhong Guo <gch981213@gmail.com> riscv: add Siflower RISC-V SoC family Kconfig support This adds Kconfig support for the Siflower RISC-V SoCs, including the SF21A6826 and SF21H8898 chips. It defines ARCH_SIFLOWER, selects the relevant T-Head errata, and conditionally selects ARM_AMBA for the UART. > diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs > index c174ac0ec46b4..9996591cd9dbf 100644 > --- a/arch/riscv/Kconfig.socs > +++ b/arch/riscv/Kconfig.socs [ ... ] > @@ -37,6 +37,13 @@ config ARCH_SIFIVE > help > This enables support for SiFive SoC platform hardware. > > +config ARCH_SIFLOWER > + bool "Siflower RISC-V SoCs" > + select ARM_AMBA if TTY Should ARM_AMBA be selected conditionally based on TTY? ARM_AMBA models the presence of a physical hardware bus, which exists regardless of whether the TTY software subsystem is enabled. Even if the PL011 UART is currently the only AMBA device on this platform, tying the hardware bus infrastructure to a software abstraction might cause issues if other AMBA devices are added later and TTY is disabled. Would it be better to select ARM_AMBA unconditionally, similar to how other architectures handle it? > + select ERRATA_THEAD > + help > + This enables support for Siflower RISC-V SoC platform hardware. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260517-sf21-topcrm-v1-0-438f2e0513ff@gmail.com?part=1 ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support 2026-05-17 14:31 ` sashiko-bot @ 2026-05-17 20:46 ` Conor Dooley 2026-05-18 14:17 ` Chuanhong Guo 0 siblings, 1 reply; 22+ messages in thread From: Conor Dooley @ 2026-05-17 20:46 UTC (permalink / raw) To: sashiko-reviews; +Cc: Chuanhong Guo, robh, conor+dt, devicetree [-- Attachment #1: Type: text/plain, Size: 1987 bytes --] On Sun, May 17, 2026 at 02:31:52PM +0000, sashiko-bot@kernel.org wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: > - [Medium] Conditionally selecting the `ARM_AMBA` hardware bus based on the `TTY` software subsystem. > -- > > commit 49824e33ae15603df7f899c7df0a4ab9b5f20eed > Author: Chuanhong Guo <gch981213@gmail.com> > > riscv: add Siflower RISC-V SoC family Kconfig support > > This adds Kconfig support for the Siflower RISC-V SoCs, including the > SF21A6826 and SF21H8898 chips. It defines ARCH_SIFLOWER, selects the > relevant T-Head errata, and conditionally selects ARM_AMBA for the UART. > > > diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs > > index c174ac0ec46b4..9996591cd9dbf 100644 > > --- a/arch/riscv/Kconfig.socs > > +++ b/arch/riscv/Kconfig.socs > [ ... ] > > @@ -37,6 +37,13 @@ config ARCH_SIFIVE > > help > > This enables support for SiFive SoC platform hardware. > > > > +config ARCH_SIFLOWER > > + bool "Siflower RISC-V SoCs" > > + select ARM_AMBA if TTY > > Should ARM_AMBA be selected conditionally based on TTY? > > ARM_AMBA models the presence of a physical hardware bus, which exists > regardless of whether the TTY software subsystem is enabled. Even if the There's no point having the driver built if there's no driver built for devices on the bus, I think is the angle here. > PL011 UART is currently the only AMBA device on this platform, tying the > hardware bus infrastructure to a software abstraction might cause issues if > other AMBA devices are added later and TTY is disabled. > > Would it be better to select ARM_AMBA unconditionally, similar to how other > architectures handle it? > > > + select ERRATA_THEAD > > + help > > + This enables support for Siflower RISC-V SoC platform hardware. > > -- > Sashiko AI review · https://sashiko.dev/#/patchset/20260517-sf21-topcrm-v1-0-438f2e0513ff@gmail.com?part=1 [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support 2026-05-17 20:46 ` Conor Dooley @ 2026-05-18 14:17 ` Chuanhong Guo 0 siblings, 0 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-18 14:17 UTC (permalink / raw) To: Conor Dooley; +Cc: sashiko-reviews, robh, conor+dt, devicetree Hi! On Mon, May 18, 2026 at 4:46 AM Conor Dooley <conor@kernel.org> wrote: > [...] > > > This enables support for SiFive SoC platform hardware. > > > > > > +config ARCH_SIFLOWER > > > + bool "Siflower RISC-V SoCs" > > > + select ARM_AMBA if TTY > > > > Should ARM_AMBA be selected conditionally based on TTY? > > > > ARM_AMBA models the presence of a physical hardware bus, which exists > > regardless of whether the TTY software subsystem is enabled. Even if the > > There's no point having the driver built if there's no driver built for > devices on the bus, I think is the angle here. > > > PL011 UART is currently the only AMBA device on this platform, tying the > > hardware bus infrastructure to a software abstraction might cause issues if > > other AMBA devices are added later and TTY is disabled. I copied this from the vendor tree. PL011 is the only peripheral on this chip requiring this flag, and there's no more AMBA-driver compatible devices on this SoC. However I'll change this one in v2 since I don't want to add the only flag in the kernel which conditionally select ARM_AMBA :P -- Regards, Chuanhong Guo ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-17 14:12 [PATCH 0/4] clk: add Siflower SF21 topcrm support Chuanhong Guo 2026-05-17 14:12 ` [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support Chuanhong Guo @ 2026-05-17 14:12 ` Chuanhong Guo 2026-05-17 14:36 ` sashiko-bot ` (2 more replies) 2026-05-17 14:12 ` [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm Chuanhong Guo 2026-05-17 14:12 ` [PATCH 4/4] clk: add support for siflower sf21-topcrm Chuanhong Guo 3 siblings, 3 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-17 14:12 UTC (permalink / raw) To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel Cc: linux-riscv, linux-kernel, linux-clk, devicetree, Chuanhong Guo Add the device tree binding header for Siflower SF21A6826/SF21H8898 toplevel clock and reset module. The header covers both clock and reset IDs provided by the block. CLK_ETH_REF_P is a clock name that exists in the vendor datasheet. This clock connects directly to CLK_PCIEPLL_FOUT2 and there's no clock gate/mux in between. An alias is created for this clock to make available clock names align with the datasheet. Signed-off-by: Chuanhong Guo <gch981213@gmail.com> --- include/dt-bindings/clock/siflower,sf21-topcrm.h | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/include/dt-bindings/clock/siflower,sf21-topcrm.h b/include/dt-bindings/clock/siflower,sf21-topcrm.h new file mode 100644 index 000000000000..3690b3452501 --- /dev/null +++ b/include/dt-bindings/clock/siflower,sf21-topcrm.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: (GPL-2.0-only OR MIT) */ + +#ifndef _DT_BINDINGS_CLK_SIFLOWER_SF21_TOPCRM_H +#define _DT_BINDINGS_CLK_SIFLOWER_SF21_TOPCRM_H + +#define SF21_CLK_CMNPLL_VCO 0 +#define SF21_CLK_CMNPLL_POSTDIV 1 + +#define SF21_CLK_DDRPLL_POSTDIV 2 + +#define SF21_CLK_PCIEPLL_VCO 3 +#define SF21_CLK_PCIEPLL_FOUT0 4 +#define SF21_CLK_PCIEPLL_FOUT1 5 +#define SF21_CLK_PCIEPLL_FOUT2 6 +#define SF21_CLK_ETH_REF_P SF21_CLK_PCIEPLL_FOUT2 +#define SF21_CLK_PCIEPLL_FOUT3 7 + +#define SF21_CLK_CPU 8 +#define SF21_CLK_PIC 9 +#define SF21_CLK_AXI 10 +#define SF21_CLK_AHB 11 +#define SF21_CLK_APB 12 +#define SF21_CLK_UART 13 +#define SF21_CLK_IRAM 14 +#define SF21_CLK_NPU 15 +#define SF21_CLK_DDRPHY_REF 16 +#define SF21_CLK_DDR_BYPASS 17 +#define SF21_CLK_ETHTSU 18 +#define SF21_CLK_GMAC_BYP_REF 19 +#define SF21_CLK_USB 20 +#define SF21_CLK_USBPHY 21 +#define SF21_CLK_SERDES_CSR 22 +#define SF21_CLK_CRYPT_CSR 23 +#define SF21_CLK_CRYPT_APP 24 +#define SF21_CLK_IROM 25 +#define SF21_CLK_BOOT 26 +#define SF21_CLK_PVT 27 +#define SF21_CLK_PLL_TEST 28 +#define SF21_CLK_PCIE_REFN 29 +#define SF21_CLK_PCIE_REFP 30 +#define SF21_CLK_MAX 31 + +#define SF21_RESET_GIC 0 +#define SF21_RESET_AXI 1 +#define SF21_RESET_AHB 2 +#define SF21_RESET_APB 3 +#define SF21_RESET_IRAM 4 +#define SF21_RESET_NPU 5 +#define SF21_RESET_DDR_CTL 6 +#define SF21_RESET_DDR_PHY 7 +#define SF21_RESET_DDR_PWR_OK_IN 8 +#define SF21_RESET_DDR_CTL_APB 9 +#define SF21_RESET_DDR_PHY_APB 10 +#define SF21_RESET_USB 11 +#define SF21_RESET_PVT 12 +#define SF21_RESET_SERDES_CSR 13 +#define SF21_RESET_CRYPT_CSR 14 +#define SF21_RESET_CRYPT_APP 15 +#define SF21_RESET_NPU2DDR_ASYNCBRIDGE 16 +#define SF21_RESET_IROM 17 +#define SF21_RESET_MAX 18 + +#endif -- 2.54.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-17 14:12 ` [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm Chuanhong Guo @ 2026-05-17 14:36 ` sashiko-bot 2026-05-17 20:47 ` Conor Dooley 2026-05-18 11:04 ` Yao Zi 2 siblings, 0 replies; 22+ messages in thread From: sashiko-bot @ 2026-05-17 14:36 UTC (permalink / raw) To: Chuanhong Guo; +Cc: conor+dt, robh, devicetree Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The dt-bindings header file exposes software-specific `MAX` macros (`SF21_CLK_MAX` and `SF21_RESET_MAX`). -- commit b39e2fdfa1953205f67f3e13ae23a09e43917c11 Author: Chuanhong Guo <gch981213@gmail.com> dt-bindings: clock: add binding header for sf21-topcrm Add the device tree binding header for Siflower SF21A6826/SF21H8898 toplevel clock and reset module. The header covers both clock and reset IDs provided by the block. > diff --git a/include/dt-bindings/clock/siflower,sf21-topcrm.h b/include/dt-bindings/clock/siflower,sf21-topcrm.h > new file mode 100644 > index 0000000000000..3690b3452501e > --- /dev/null > +++ b/include/dt-bindings/clock/siflower,sf21-topcrm.h [ ... ] > +#define SF21_CLK_PCIE_REFN 29 > +#define SF21_CLK_PCIE_REFP 30 > +#define SF21_CLK_MAX 31 Should this MAX macro be exposed in the dt-bindings header? Device tree binding headers define the hardware ABI, and the total number of clocks is an internal software implementation detail. Changing this value when new clocks are added in the future breaks ABI compatibility for components compiled against the older header. Could this macro be defined internally within the driver source code instead? > + > +#define SF21_RESET_GIC 0 [ ... ] > +#define SF21_RESET_NPU2DDR_ASYNCBRIDGE 16 > +#define SF21_RESET_IROM 17 > +#define SF21_RESET_MAX 18 Similarly, should SF21_RESET_MAX be moved to the driver source code to avoid breaking ABI compatibility if new resets are added? > + > +#endif -- Sashiko AI review · https://sashiko.dev/#/patchset/20260517-sf21-topcrm-v1-0-438f2e0513ff@gmail.com?part=2 ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-17 14:12 ` [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm Chuanhong Guo 2026-05-17 14:36 ` sashiko-bot @ 2026-05-17 20:47 ` Conor Dooley 2026-05-17 20:51 ` Conor Dooley 2026-05-18 11:04 ` Yao Zi 2 siblings, 1 reply; 22+ messages in thread From: Conor Dooley @ 2026-05-17 20:47 UTC (permalink / raw) To: Chuanhong Guo Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree [-- Attachment #1: Type: text/plain, Size: 329 bytes --] On Sun, May 17, 2026 at 10:12:56PM +0800, Chuanhong Guo wrote: > +#define SF21_CLK_PCIE_REFP 30 > +#define SF21_CLK_MAX 31 > +#define SF21_RESET_IROM 17 > +#define SF21_RESET_MAX 18 Having _MAX is not permitted, but is also really confusing that "max" appears to be max+1, so actually represents the count not the max? [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-17 20:47 ` Conor Dooley @ 2026-05-17 20:51 ` Conor Dooley 2026-05-18 11:42 ` Chuanhong Guo 0 siblings, 1 reply; 22+ messages in thread From: Conor Dooley @ 2026-05-17 20:51 UTC (permalink / raw) To: Chuanhong Guo Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree [-- Attachment #1: Type: text/plain, Size: 693 bytes --] On Sun, May 17, 2026 at 09:47:26PM +0100, Conor Dooley wrote: > On Sun, May 17, 2026 at 10:12:56PM +0800, Chuanhong Guo wrote: > > +#define SF21_CLK_PCIE_REFP 30 > > +#define SF21_CLK_MAX 31 > > > +#define SF21_RESET_IROM 17 > > +#define SF21_RESET_MAX 18 > > > Having _MAX is not permitted, but is also really confusing that "max" > appears to be max+1, so actually represents the count not the max? To be clear, if you need a define like this, put it in the driver. Bindings having a "_MAX" or "NUM_CLKS" doesn't make sense, and half the time this number ends up changing anyway. Also, squash this with the patch adding the clock binding. Cheers, Conor. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-17 20:51 ` Conor Dooley @ 2026-05-18 11:42 ` Chuanhong Guo 0 siblings, 0 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-18 11:42 UTC (permalink / raw) To: Conor Dooley Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree On Mon, May 18, 2026 at 4:52 AM Conor Dooley <conor@kernel.org> wrote: > > On Sun, May 17, 2026 at 09:47:26PM +0100, Conor Dooley wrote: > > On Sun, May 17, 2026 at 10:12:56PM +0800, Chuanhong Guo wrote: > > > +#define SF21_CLK_PCIE_REFP 30 > > > +#define SF21_CLK_MAX 31 > > > > > +#define SF21_RESET_IROM 17 > > > +#define SF21_RESET_MAX 18 > > > > > > Having _MAX is not permitted, but is also really confusing that "max" > > appears to be max+1, so actually represents the count not the max? > > To be clear, if you need a define like this, put it in the driver. > Bindings having a "_MAX" or "NUM_CLKS" doesn't make sense, Sure. I'll move it to the driver and rename it in v2. > and half the > time this number ends up changing anyway. This one will definitely change later for the two clocks I can't test yet. > Also, squash this with the patch adding the clock binding. OK. I'll do so in v2. -- Regards, Chuanhong Guo ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-17 14:12 ` [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm Chuanhong Guo 2026-05-17 14:36 ` sashiko-bot 2026-05-17 20:47 ` Conor Dooley @ 2026-05-18 11:04 ` Yao Zi 2026-05-18 11:43 ` Chuanhong Guo 2 siblings, 1 reply; 22+ messages in thread From: Yao Zi @ 2026-05-18 11:04 UTC (permalink / raw) To: Chuanhong Guo, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel Cc: linux-riscv, linux-kernel, linux-clk, devicetree On Sun, May 17, 2026 at 10:12:56PM +0800, Chuanhong Guo wrote: > Add the device tree binding header for Siflower SF21A6826/SF21H8898 > toplevel clock and reset module. The header covers both clock and > reset IDs provided by the block. Would it be better to split reset stuff into a separate header in dt-bindings/reset, so it ends up with a clearer structure? This shouldn't cost a lot. Regards, Yao Zi > CLK_ETH_REF_P is a clock name that exists in the vendor datasheet. > This clock connects directly to CLK_PCIEPLL_FOUT2 and there's no > clock gate/mux in between. An alias is created for this clock > to make available clock names align with the datasheet. > > Signed-off-by: Chuanhong Guo <gch981213@gmail.com> > --- > include/dt-bindings/clock/siflower,sf21-topcrm.h | 63 ++++++++++++++++++++++++ > 1 file changed, 63 insertions(+) ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm 2026-05-18 11:04 ` Yao Zi @ 2026-05-18 11:43 ` Chuanhong Guo 0 siblings, 0 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-18 11:43 UTC (permalink / raw) To: Yao Zi Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree On Mon, May 18, 2026 at 7:05 PM Yao Zi <me@ziyao.cc> wrote: > > On Sun, May 17, 2026 at 10:12:56PM +0800, Chuanhong Guo wrote: > > Add the device tree binding header for Siflower SF21A6826/SF21H8898 > > toplevel clock and reset module. The header covers both clock and > > reset IDs provided by the block. > > Would it be better to split reset stuff into a separate header in > dt-bindings/reset, so it ends up with a clearer structure? This > shouldn't cost a lot. > Sure. I'll split it in v2. -- Regards, Chuanhong Guo ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm 2026-05-17 14:12 [PATCH 0/4] clk: add Siflower SF21 topcrm support Chuanhong Guo 2026-05-17 14:12 ` [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support Chuanhong Guo 2026-05-17 14:12 ` [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm Chuanhong Guo @ 2026-05-17 14:12 ` Chuanhong Guo 2026-05-17 15:35 ` Rob Herring (Arm) 2026-05-17 20:50 ` Conor Dooley 2026-05-17 14:12 ` [PATCH 4/4] clk: add support for siflower sf21-topcrm Chuanhong Guo 3 siblings, 2 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-17 14:12 UTC (permalink / raw) To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel Cc: linux-riscv, linux-kernel, linux-clk, devicetree, Chuanhong Guo Add a binding doc for the top clock and reset module found on Siflower SF21 SoCs. This block provides the main PLLs, high-level clock controls, and some reset lines. Signed-off-by: Chuanhong Guo <gch981213@gmail.com> --- .../bindings/clock/siflower,sf21-topcrm.yaml | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.yaml b/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.yaml new file mode 100644 index 000000000000..a013d48841f4 --- /dev/null +++ b/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/clock/siflower,sf21-topcrm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Siflower SF21 toplevel clock and reset module + +maintainers: + - Chuanhong Guo <gch981213@gmail.com> + +description: | + The toplevel clock and reset module on Siflower SF21 SoCs manages + the main PLLs, high-level clock muxes/dividers/gates, and some + reset lines. + Available clocks and resets are defined in: + include/dt-bindings/clock/siflower,sf21-topcrm.h + +properties: + compatible: + const: siflower,sf21-topcrm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + const: xin25m + + "#clock-cells": + const: 1 + + "#reset-cells": + const: 1 + +required: + - compatible + - reg + - clocks + - clock-names + - "#clock-cells" + - "#reset-cells" + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/siflower,sf21-topcrm.h> + / { + #address-cells = <1>; + #size-cells = <1>; + + xin25m: clock-25000000 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <25000000>; + }; + + clock-controller@ce00400 { + compatible = "siflower,sf21-topcrm"; + reg = <0x0ce00400 0x400>; + clocks = <&xin25m>; + clock-names = "xin25m"; + #clock-cells = <1>; + #reset-cells = <1>; + }; + }; -- 2.54.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm 2026-05-17 14:12 ` [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm Chuanhong Guo @ 2026-05-17 15:35 ` Rob Herring (Arm) 2026-05-17 20:50 ` Conor Dooley 1 sibling, 0 replies; 22+ messages in thread From: Rob Herring (Arm) @ 2026-05-17 15:35 UTC (permalink / raw) To: Chuanhong Guo Cc: Palmer Dabbelt, Conor Dooley, Michael Turquette, Paul Walmsley, Alexandre Ghiti, linux-riscv, Philipp Zabel, Stephen Boyd, linux-clk, devicetree, Krzysztof Kozlowski, linux-kernel, Brian Masney, Albert Ou On Sun, 17 May 2026 22:12:57 +0800, Chuanhong Guo wrote: > Add a binding doc for the top clock and reset module found on Siflower > SF21 SoCs. This block provides the main PLLs, high-level clock > controls, and some reset lines. > > Signed-off-by: Chuanhong Guo <gch981213@gmail.com> > --- > .../bindings/clock/siflower,sf21-topcrm.yaml | 69 ++++++++++++++++++++++ > 1 file changed, 69 insertions(+) > My bot found errors running 'make dt_binding_check' on your patch: yamllint warnings/errors: dtschema/dtc warnings/errors: /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.example.dtb: /: 'compatible' is a required property from schema $id: http://devicetree.org/schemas/root-node.yaml /builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.example.dtb: /: 'model' is a required property from schema $id: http://devicetree.org/schemas/root-node.yaml doc reference errors (make refcheckdocs): See https://patchwork.kernel.org/project/devicetree/patch/20260517-sf21-topcrm-v1-3-438f2e0513ff@gmail.com The base for the series is generally the latest rc1. A different dependency should be noted in *this* patch. If you already ran 'make dt_binding_check' and didn't see the above error(s), then make sure 'yamllint' is installed and dt-schema is up to date: pip3 install dtschema --upgrade Please check and re-submit after running the above command yourself. Note that DT_SCHEMA_FILES can be set to your schema file to speed up checking your schema. However, it must be unset to test all examples with your schema. ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm 2026-05-17 14:12 ` [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm Chuanhong Guo 2026-05-17 15:35 ` Rob Herring (Arm) @ 2026-05-17 20:50 ` Conor Dooley 2026-05-18 12:12 ` Chuanhong Guo 1 sibling, 1 reply; 22+ messages in thread From: Conor Dooley @ 2026-05-17 20:50 UTC (permalink / raw) To: Chuanhong Guo Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree [-- Attachment #1: Type: text/plain, Size: 2928 bytes --] On Sun, May 17, 2026 at 10:12:57PM +0800, Chuanhong Guo wrote: > Add a binding doc for the top clock and reset module found on Siflower > SF21 SoCs. This block provides the main PLLs, high-level clock > controls, and some reset lines. > > Signed-off-by: Chuanhong Guo <gch981213@gmail.com> > --- > .../bindings/clock/siflower,sf21-topcrm.yaml | 69 ++++++++++++++++++++++ > 1 file changed, 69 insertions(+) > > diff --git a/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.yaml b/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.yaml > new file mode 100644 > index 000000000000..a013d48841f4 > --- /dev/null > +++ b/Documentation/devicetree/bindings/clock/siflower,sf21-topcrm.yaml > @@ -0,0 +1,69 @@ > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) > +%YAML 1.2 > +--- > +$id: http://devicetree.org/schemas/clock/siflower,sf21-topcrm.yaml# > +$schema: http://devicetree.org/meta-schemas/core.yaml# > + > +title: Siflower SF21 toplevel clock and reset module > + > +maintainers: > + - Chuanhong Guo <gch981213@gmail.com> > + > +description: | > + The toplevel clock and reset module on Siflower SF21 SoCs manages > + the main PLLs, high-level clock muxes/dividers/gates, and some > + reset lines. > + Available clocks and resets are defined in: > + include/dt-bindings/clock/siflower,sf21-topcrm.h > + > +properties: > + compatible: > + const: siflower,sf21-topcrm > + > + reg: > + maxItems: 1 > + > + clocks: > + maxItems: 1 > + > + clock-names: > + const: xin25m Is a 25 MHz reference required on this SoC? If not, name here is obviously problematic if it can be something else. Not much value in clock-names anyway when you only have 1. > + > + "#clock-cells": > + const: 1 > + > + "#reset-cells": > + const: 1 > + > +required: > + - compatible > + - reg > + - clocks > + - clock-names > + - "#clock-cells" > + - "#reset-cells" > + > +additionalProperties: false > + > +examples: > + - | > + #include <dt-bindings/clock/siflower,sf21-topcrm.h> > + / { Replace this / with "soc". > + #address-cells = <1>; > + #size-cells = <1>; > + > + xin25m: clock-25000000 { > + compatible = "fixed-clock"; > + #clock-cells = <0>; > + clock-frequency = <25000000>; > + }; Delete this node, it's not needed in the example. The tooling will fill it in. Also, please test your bindings since this doesn't pass. pw-bot: changes-requested Thanks, Conor. > + > + clock-controller@ce00400 { > + compatible = "siflower,sf21-topcrm"; > + reg = <0x0ce00400 0x400>; > + clocks = <&xin25m>; > + clock-names = "xin25m"; > + #clock-cells = <1>; > + #reset-cells = <1>; > + }; > + }; > > -- > 2.54.0 > [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm 2026-05-17 20:50 ` Conor Dooley @ 2026-05-18 12:12 ` Chuanhong Guo 2026-05-18 12:28 ` Yao Zi 0 siblings, 1 reply; 22+ messages in thread From: Chuanhong Guo @ 2026-05-18 12:12 UTC (permalink / raw) To: Conor Dooley Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree Hi! On Mon, May 18, 2026 at 4:50 AM Conor Dooley <conor@kernel.org> wrote: > > [...] > > + > > + clock-names: > > + const: xin25m > > Is a 25 MHz reference required on this SoC? If not, name here is > obviously problematic if it can be something else. > Not much value in clock-names anyway when you only have 1. You are right. I'll drop clock-names and use index to grab the parent I needed in the driver in v2. > > > + > > + "#clock-cells": > > + const: 1 > > + > > + "#reset-cells": > > + const: 1 > > + > > +required: > > + - compatible > > + - reg > > + - clocks > > + - clock-names > > + - "#clock-cells" > > + - "#reset-cells" > > + > > +additionalProperties: false > > + > > +examples: > > + - | > > + #include <dt-bindings/clock/siflower,sf21-topcrm.h> > > + / { > > Replace this / with "soc". Will do so in v2. > > > + #address-cells = <1>; > > + #size-cells = <1>; > > + > > + xin25m: clock-25000000 { > > + compatible = "fixed-clock"; > > + #clock-cells = <0>; > > + clock-frequency = <25000000>; > > + }; > > Delete this node, it's not needed in the example. The tooling will fill > it in. Oh, I didn't know that. I'll drop it in v2. > > Also, please test your bindings since this doesn't pass. > > pw-bot: changes-requested It's failing on the example as root node missing "model" and "compatible". and it will be fixed after changing "/" to "soc". I'll remember to run the full dt check instead of using DT_SCHEMA_FILES for my single file next time. -- Regards, Chuanhong Guo ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm 2026-05-18 12:12 ` Chuanhong Guo @ 2026-05-18 12:28 ` Yao Zi 0 siblings, 0 replies; 22+ messages in thread From: Yao Zi @ 2026-05-18 12:28 UTC (permalink / raw) To: Chuanhong Guo, Conor Dooley Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree On Mon, May 18, 2026 at 08:12:14PM +0800, Chuanhong Guo wrote: > Hi! > > On Mon, May 18, 2026 at 4:50 AM Conor Dooley <conor@kernel.org> wrote: ... > > > +examples: > > > + - | > > > + #include <dt-bindings/clock/siflower,sf21-topcrm.h> Though it isn't a big problem, the include is unnecessary, either, since you don't make use of any constants from the binding header in the example. > > > + / { > > > > Replace this / with "soc". > > Will do so in v2. > > > > > > + #address-cells = <1>; > > > + #size-cells = <1>; > > > + > > > + xin25m: clock-25000000 { > > > + compatible = "fixed-clock"; > > > + #clock-cells = <0>; > > > + clock-frequency = <25000000>; > > > + }; > > > > Delete this node, it's not needed in the example. The tooling will fill > > it in. > > Oh, I didn't know that. I'll drop it in v2. > > > > > Also, please test your bindings since this doesn't pass. > > > > pw-bot: changes-requested > > It's failing on the example as root node missing "model" and "compatible". > and it will be fixed after changing "/" to "soc". > I'll remember to run the full dt check instead of using DT_SCHEMA_FILES > for my single file next time. Alternatively you could choose to drop the outer node and keep the clock-controller node only. > > -- > Regards, > Chuanhong Guo Best regards, Yao Zi ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 4/4] clk: add support for siflower sf21-topcrm 2026-05-17 14:12 [PATCH 0/4] clk: add Siflower SF21 topcrm support Chuanhong Guo ` (2 preceding siblings ...) 2026-05-17 14:12 ` [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm Chuanhong Guo @ 2026-05-17 14:12 ` Chuanhong Guo 2026-05-17 15:09 ` sashiko-bot 2026-05-18 12:21 ` Yao Zi 3 siblings, 2 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-17 14:12 UTC (permalink / raw) To: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel Cc: linux-riscv, linux-kernel, linux-clk, devicetree, Chuanhong Guo This commit adds a driver for the Toplevel clock and reset controller found on Siflower SF21A6826/SF21H8898 SoCs. This block contains control for 3 PLLs, several clock mux/gate/divider blocks, and a reset register for on-chip peripherals. There are also two registers for enabling PCIE clock output in this block. They aren't covered by this patch because I can't test those without a PCIE driver. These will be added with the PCIE driver patchset later after I get that working. Signed-off-by: Chuanhong Guo <gch981213@gmail.com> --- drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/siflower/Kconfig | 22 + drivers/clk/siflower/Makefile | 1 + drivers/clk/siflower/clk-sf21-topcrm.c | 1053 ++++++++++++++++++++++++++++++++ 5 files changed, 1078 insertions(+) diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index b2efbe9f6acb..8098e38d5f59 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -523,6 +523,7 @@ source "drivers/clk/renesas/Kconfig" source "drivers/clk/rockchip/Kconfig" source "drivers/clk/samsung/Kconfig" source "drivers/clk/sifive/Kconfig" +source "drivers/clk/siflower/Kconfig" source "drivers/clk/socfpga/Kconfig" source "drivers/clk/sophgo/Kconfig" source "drivers/clk/spacemit/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index a3e2862ebd7e..7492942b7fad 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -144,6 +144,7 @@ obj-y += renesas/ obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/ obj-$(CONFIG_CLK_SIFIVE) += sifive/ +obj-$(CONFIG_CLK_SIFLOWER) += siflower/ obj-y += socfpga/ obj-y += sophgo/ obj-y += spacemit/ diff --git a/drivers/clk/siflower/Kconfig b/drivers/clk/siflower/Kconfig new file mode 100644 index 000000000000..03cbfbdbdb8d --- /dev/null +++ b/drivers/clk/siflower/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 + +menuconfig CLK_SIFLOWER + bool "Clock driver for Siflower SoCs" + depends on ARCH_SIFLOWER || COMPILE_TEST + default ARCH_SIFLOWER + help + Clock drivers for Siflower Linux-capable SoCs. + +if CLK_SIFLOWER + +config CLK_SF21_TOPCRM + tristate "Clock driver for Siflower SF21 toplevel clock & reset module" + depends on ARCH_SIFLOWER || COMPILE_TEST + default ARCH_SIFLOWER + select RESET_CONTROLLER + help + Supports the toplevel clock and reset module in Siflower SF21 SoCs. + If this kernel is meant to run on Siflower SF21A6826 or SF21H8898, + enable this driver. + +endif diff --git a/drivers/clk/siflower/Makefile b/drivers/clk/siflower/Makefile new file mode 100644 index 000000000000..952a470a4308 --- /dev/null +++ b/drivers/clk/siflower/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_CLK_SF21_TOPCRM) += clk-sf21-topcrm.o diff --git a/drivers/clk/siflower/clk-sf21-topcrm.c b/drivers/clk/siflower/clk-sf21-topcrm.c new file mode 100644 index 000000000000..7d4c5e370d6d --- /dev/null +++ b/drivers/clk/siflower/clk-sf21-topcrm.c @@ -0,0 +1,1053 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/clk-provider.h> +#include <linux/bitfield.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/rational.h> +#include <linux/module.h> +#include <linux/reset-controller.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <dt-bindings/clock/siflower,sf21-topcrm.h> + +struct sf_clk_common { + void __iomem *base; + /* Serializes register RMW sequences shared by clocks and resets. */ + spinlock_t *lock; + struct clk_hw hw; +}; + +static inline struct sf_clk_common *hw_to_sf_clk_common(struct clk_hw *hw) +{ + return container_of(hw, struct sf_clk_common, hw); +} + +static inline u32 sf_readl(struct sf_clk_common *priv, u32 reg) +{ + return readl(priv->base + reg); +} + +static inline void sf_writel(struct sf_clk_common *priv, u32 reg, u32 val) +{ + return writel(val, priv->base + reg); +} + +static inline void sf_rmw(struct sf_clk_common *priv, u32 reg, u32 clr, u32 set) +{ + u32 val; + + val = sf_readl(priv, reg); + val &= ~clr; + val |= set; + sf_writel(priv, reg, val); +} + +#define PLL_CMN_CFG1 0x0 +#define PLL_CMN_BYPASS BIT(27) +#define PLL_CMN_PD BIT(26) +#define PLL_CMN_FBDIV GENMASK(25, 14) +#define PLL_CMN_FBDIV_BITS (25 - 14 + 1) +#define PLL_CMN_POSTDIV_PD BIT(13) +#define PLL_CMN_VCO_PD BIT(12) +#define PLL_CMN_POSTDIV1 GENMASK(11, 9) +#define PLL_CMN_POSTDIV2 GENMASK(8, 6) +#define PLL_CMN_REFDIV GENMASK(5, 0) +#define PLL_CMN_REFDIV_BITS 6 + +#define PLL_CMN_LOCK 0xc8 +#define PLL_DDR_LOCK 0xcc +#define PLL_PCIE_LOCK 0xd4 + +#define CFG_LOAD 0x100 +#define CFG_LOAD_PCIE_PLL BIT(4) +#define CFG_LOAD_DDR_PLL BIT(2) +#define CFG_LOAD_CMN_PLL BIT(1) +#define CFG_LOAD_DIV BIT(0) + +#define PLL_LOCK_TIMEOUT_US 1000 + +static unsigned long sf21_cmnpll_vco_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + u32 cfg = sf_readl(priv, PLL_CMN_CFG1); + unsigned long refdiv = FIELD_GET(PLL_CMN_REFDIV, cfg); + unsigned long fbdiv = FIELD_GET(PLL_CMN_FBDIV, cfg); + + if (!refdiv || !fbdiv) + return 0; + + return (parent_rate / refdiv) * fbdiv; +} + +static int sf21_cmnpll_vco_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned long fbdiv, refdiv; + + rational_best_approximation(req->rate, req->best_parent_rate, + BIT(PLL_CMN_FBDIV_BITS) - 1, + BIT(PLL_CMN_REFDIV_BITS) - 1, &fbdiv, + &refdiv); + if (!refdiv || !fbdiv) + return -EINVAL; + + req->rate = (req->best_parent_rate / refdiv) * fbdiv; + + return 0; +} + +static int sf21_cmnpll_vco_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + unsigned long flags; + unsigned long fbdiv, refdiv; + u32 val; + int ret; + + rational_best_approximation(rate, parent_rate, + BIT(PLL_CMN_FBDIV_BITS) - 1, + BIT(PLL_CMN_REFDIV_BITS) - 1, &fbdiv, + &refdiv); + if (!refdiv || !fbdiv) + return -EINVAL; + + spin_lock_irqsave(priv->lock, flags); + + sf_rmw(priv, PLL_CMN_CFG1, PLL_CMN_REFDIV | PLL_CMN_FBDIV | PLL_CMN_PD, + FIELD_PREP(PLL_CMN_REFDIV, refdiv) | + FIELD_PREP(PLL_CMN_FBDIV, fbdiv)); + sf_writel(priv, CFG_LOAD, CFG_LOAD_CMN_PLL); + sf_writel(priv, CFG_LOAD, 0); + + ret = readl_poll_timeout_atomic(priv->base + PLL_CMN_LOCK, val, val & 1, + 0, PLL_LOCK_TIMEOUT_US); + if (ret) + goto out_unlock; + +out_unlock: + spin_unlock_irqrestore(priv->lock, flags); + return ret; +} + +static const struct clk_ops sf21_cmnpll_vco_ops = { + .recalc_rate = sf21_cmnpll_vco_recalc_rate, + .determine_rate = sf21_cmnpll_vco_determine_rate, + .set_rate = sf21_cmnpll_vco_set_rate, +}; + +static struct sf_clk_common cmnpll_vco = { + .hw.init = CLK_HW_INIT_FW_NAME("cmnpll_vco", "xin25m", + &sf21_cmnpll_vco_ops, 0), +}; + +static unsigned long sf21_dualdiv_round_rate(unsigned long rate, + unsigned long parent_rate, + unsigned int range, + unsigned int *diva, + unsigned int *divb) +{ + unsigned int div = DIV_ROUND_CLOSEST(parent_rate, rate); + unsigned int best_diff, da, db, cur_div, cur_diff; + + if (div <= 1) { + *diva = 1; + *divb = 1; + return parent_rate; + } + + best_diff = div - 1; + *diva = 1; + *divb = 1; + + for (da = 1; da <= range; da++) { + db = DIV_ROUND_CLOSEST(div, da); + if (db > da) + db = da; + + cur_div = da * db; + if (div > cur_div) + cur_diff = div - cur_div; + else + cur_diff = cur_div - div; + + if (cur_diff < best_diff) { + best_diff = cur_diff; + *diva = da; + *divb = db; + } + if (cur_diff == 0) + break; + } + + return parent_rate / *diva / *divb; +} + +static int sf21_cmnpll_postdiv_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned int diva, divb; + + if (!req->rate) + return -EINVAL; + + req->rate = sf21_dualdiv_round_rate(req->rate, req->best_parent_rate, + 7, &diva, &divb); + + return 0; +} + +static int sf21_cmnpll_postdiv_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + unsigned int diva, divb; + unsigned long flags; + + if (!rate) + return -EINVAL; + + sf21_dualdiv_round_rate(rate, parent_rate, 7, &diva, &divb); + + spin_lock_irqsave(priv->lock, flags); + sf_rmw(priv, PLL_CMN_CFG1, PLL_CMN_POSTDIV1 | PLL_CMN_POSTDIV2, + FIELD_PREP(PLL_CMN_POSTDIV1, diva) | + FIELD_PREP(PLL_CMN_POSTDIV2, divb)); + sf_writel(priv, CFG_LOAD, CFG_LOAD_CMN_PLL); + sf_writel(priv, CFG_LOAD, 0); + spin_unlock_irqrestore(priv->lock, flags); + return 0; +} + +static unsigned long +sf21_cmnpll_postdiv_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + u32 cfg = sf_readl(priv, PLL_CMN_CFG1); + unsigned long div1 = FIELD_GET(PLL_CMN_POSTDIV1, cfg); + unsigned long div2 = FIELD_GET(PLL_CMN_POSTDIV2, cfg); + + if (!div1 || !div2) + return 0; + + return parent_rate / div1 / div2; +} + +static const struct clk_ops sf21_cmnpll_postdiv_ops = { + .recalc_rate = sf21_cmnpll_postdiv_recalc_rate, + .determine_rate = sf21_cmnpll_postdiv_determine_rate, + .set_rate = sf21_cmnpll_postdiv_set_rate, +}; + +static struct sf_clk_common cmnpll_postdiv = { + .hw.init = CLK_HW_INIT_HW("cmnpll_postdiv", &cmnpll_vco.hw, + &sf21_cmnpll_postdiv_ops, 0), +}; + +#define PLL_DDR_CFG1 0x18 +#define PLL_DDR_BYPASS BIT(23) +#define PLL_DDR_PLLEN BIT(22) +#define PLL_DDR_4PHASEEN BIT(21) +#define PLL_DDR_POSTDIVEN BIT(20) +#define PLL_DDR_DSMEN BIT(19) +#define PLL_DDR_DACEN BIT(18) +#define PLL_DDR_DSKEWCALBYP BIT(17) +#define PLL_DDR_DSKEWCALCNT GENMASK(16, 14) +#define PLL_DDR_DSKEWCALEN BIT(13) +#define PLL_DDR_DSKEWCALIN GENMASK(12, 1) +#define PLL_DDR_DSKEWFASTCAL BIT(0) + +#define PLL_DDR_CFG2 0x1c +#define PLL_DDR_POSTDIV1 GENMASK(29, 27) +#define PLL_DDR_POSTDIV2 GENMASK(26, 24) +#define PLL_DDR_FRAC GENMASK(23, 0) + +#define PLL_DDR_CFG3 0x20 +#define PLL_DDR_FBDIV GENMASK(17, 6) +#define PLL_DDR_REFDIV GENMASK(5, 0) + +static unsigned long +sf21_ddrpll_postdiv_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + u32 cfg2 = sf_readl(priv, PLL_DDR_CFG2); + u32 postdiv1 = FIELD_GET(PLL_DDR_POSTDIV1, cfg2); + u32 postdiv2 = FIELD_GET(PLL_DDR_POSTDIV2, cfg2); + u32 cfg3 = sf_readl(priv, PLL_DDR_CFG3); + u32 fbdiv = FIELD_GET(PLL_DDR_FBDIV, cfg3); + u32 refdiv = FIELD_GET(PLL_DDR_REFDIV, cfg3); + + if (!refdiv || !fbdiv || !postdiv1 || !postdiv2) + return 0; + + return (parent_rate / refdiv) * fbdiv / postdiv1 / postdiv2; +} + +static const struct clk_ops sf21_ddrpll_postdiv_ops = { + .recalc_rate = sf21_ddrpll_postdiv_recalc_rate, +}; + +static struct sf_clk_common ddrpll_postdiv = { + .hw.init = CLK_HW_INIT_FW_NAME("ddrpll_postdiv", "xin25m", + &sf21_ddrpll_postdiv_ops, 0), +}; + +#define PLL_PCIE_CFG1 0x4c +#define PLL_PCIE_PLLEN BIT(31) +#define PLL_PCIE_POSTDIV0PRE BIT(30) +#define PLL_PCIE_REFDIV GENMASK(29, 24) +#define PLL_PCIE_FRAC GENMASK(23, 0) + +#define PLL_PCIE_CFG2 0x50 +#define PLL_PCIE_FOUTEN(i) BIT(28 + (i)) +#define PLL_PCIE_BYPASS(i) BIT(24 + (i)) +#define PLL_PCIE_PDIVA_OFFS(i) (21 - 6 * (i)) +#define PLL_PCIE_PDIVB_OFFS(i) (18 - 6 * (i)) +#define PLL_PCIE_PDIV_MASK GENMASK(2, 0) + +#define PLL_PCIE_CFG3 0x54 +#define PLL_PCIE_DSKEWFASTCAL BIT(31) +#define PLL_PCIE_DACEN BIT(30) +#define PLL_PCIE_DSMEN BIT(29) +#define PLL_PCIE_DSKEWCALEN BIT(28) +#define PLL_PCIE_DSKEWCALBYP BIT(27) +#define PLL_PCIE_DSKEWCALCNT GENMASK(26, 24) +#define PLL_PCIE_DSKEWCALIN GENMASK(23, 12) +#define PLL_PCIE_FBDIV GENMASK(11, 0) + +static unsigned long +sf21_pciepll_vco_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + u32 cfg1 = sf_readl(priv, PLL_PCIE_CFG1); + unsigned long refdiv = FIELD_GET(PLL_PCIE_REFDIV, cfg1); + u32 cfg3 = sf_readl(priv, PLL_PCIE_CFG3); + unsigned long fbdiv = FIELD_GET(PLL_PCIE_FBDIV, cfg3); + + if (!refdiv || !fbdiv) + return 0; + + return (parent_rate / refdiv) * fbdiv / 4; +} + +static int sf21_pciepll_vco_enable(struct clk_hw *hw) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + unsigned long flags; + u32 val; + int ret; + + spin_lock_irqsave(priv->lock, flags); + sf_rmw(priv, PLL_PCIE_CFG1, 0, PLL_PCIE_PLLEN); + sf_writel(priv, CFG_LOAD, CFG_LOAD_PCIE_PLL); + sf_writel(priv, CFG_LOAD, 0); + ret = readl_poll_timeout_atomic(priv->base + PLL_PCIE_LOCK, val, val & 1, + 0, PLL_LOCK_TIMEOUT_US); + spin_unlock_irqrestore(priv->lock, flags); + return ret; +} + +static void sf21_pciepll_vco_disable(struct clk_hw *hw) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + unsigned long flags; + + spin_lock_irqsave(priv->lock, flags); + sf_rmw(priv, PLL_PCIE_CFG1, PLL_PCIE_PLLEN, 0); + sf_writel(priv, CFG_LOAD, CFG_LOAD_PCIE_PLL); + sf_writel(priv, CFG_LOAD, 0); + spin_unlock_irqrestore(priv->lock, flags); +} + +static int sf21_pciepll_vco_is_enabled(struct clk_hw *hw) +{ + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); + + return !!(sf_readl(priv, PLL_PCIE_CFG1) & PLL_PCIE_PLLEN); +} + +static const struct clk_ops sf21_pciepll_vco_ops = { + .enable = sf21_pciepll_vco_enable, + .disable = sf21_pciepll_vco_disable, + .is_enabled = sf21_pciepll_vco_is_enabled, + .recalc_rate = sf21_pciepll_vco_recalc_rate, +}; + +static struct sf_clk_common pciepll_vco = { + .hw.init = CLK_HW_INIT_FW_NAME("pciepll_vco", "xin25m", + &sf21_pciepll_vco_ops, + CLK_SET_RATE_GATE), +}; + +struct sf21_pciepll_fout { + struct sf_clk_common common; + u8 index; +}; + +static int sf21_pciepll_fout_enable(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_pciepll_fout *priv = + container_of(cmn_priv, struct sf21_pciepll_fout, common); + unsigned long flags; + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, PLL_PCIE_CFG2, 0, PLL_PCIE_FOUTEN(priv->index)); + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_PCIE_PLL); + sf_writel(cmn_priv, CFG_LOAD, 0); + spin_unlock_irqrestore(cmn_priv->lock, flags); + return 0; +} + +static void sf21_pciepll_fout_disable(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_pciepll_fout *priv = + container_of(cmn_priv, struct sf21_pciepll_fout, common); + unsigned long flags; + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, PLL_PCIE_CFG2, PLL_PCIE_FOUTEN(priv->index), 0); + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_PCIE_PLL); + sf_writel(cmn_priv, CFG_LOAD, 0); + spin_unlock_irqrestore(cmn_priv->lock, flags); +} + +static int sf21_pciepll_fout_is_enabled(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_pciepll_fout *priv = + container_of(cmn_priv, struct sf21_pciepll_fout, common); + + return !!(sf_readl(cmn_priv, PLL_PCIE_CFG2) & + PLL_PCIE_FOUTEN(priv->index)); +} + +static int sf21_pciepll_fout_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned int diva, divb; + + if (!req->rate) + return -EINVAL; + + req->rate = sf21_dualdiv_round_rate(req->rate, req->best_parent_rate, + 8, &diva, &divb); + + return 0; +} + +static int sf21_pciepll_fout_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_pciepll_fout *priv = + container_of(cmn_priv, struct sf21_pciepll_fout, common); + unsigned int diva, divb; + unsigned long flags; + + if (!rate) + return -EINVAL; + + sf21_dualdiv_round_rate(rate, parent_rate, 8, &diva, &divb); + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, PLL_PCIE_CFG2, + (PLL_PCIE_PDIV_MASK << PLL_PCIE_PDIVA_OFFS(priv->index)) | + (PLL_PCIE_PDIV_MASK << PLL_PCIE_PDIVB_OFFS(priv->index)), + ((diva - 1) << PLL_PCIE_PDIVA_OFFS(priv->index)) | + ((divb - 1) << PLL_PCIE_PDIVB_OFFS(priv->index))); + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_PCIE_PLL); + sf_writel(cmn_priv, CFG_LOAD, 0); + spin_unlock_irqrestore(cmn_priv->lock, flags); + return 0; +} + +static unsigned long +sf21_pciepll_fout_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_pciepll_fout *priv = + container_of(cmn_priv, struct sf21_pciepll_fout, common); + int idx = priv->index; + u32 cfg2 = sf_readl(cmn_priv, PLL_PCIE_CFG2); + ulong pdiva = (cfg2 >> PLL_PCIE_PDIVA_OFFS(idx)) & PLL_PCIE_PDIV_MASK; + ulong pdivb = (cfg2 >> PLL_PCIE_PDIVB_OFFS(idx)) & PLL_PCIE_PDIV_MASK; + + return parent_rate / (pdiva + 1) / (pdivb + 1); +} + +static const struct clk_ops sf21_pciepll_fout_ops = { + .enable = sf21_pciepll_fout_enable, + .disable = sf21_pciepll_fout_disable, + .is_enabled = sf21_pciepll_fout_is_enabled, + .recalc_rate = sf21_pciepll_fout_recalc_rate, + .determine_rate = sf21_pciepll_fout_determine_rate, + .set_rate = sf21_pciepll_fout_set_rate, +}; + +#define SF21_PCIEPLL_FOUT(_name, _idx, _flags) \ + struct sf21_pciepll_fout _name = { \ + .common.hw.init = CLK_HW_INIT_HW(#_name, \ + &pciepll_vco.hw, \ + &sf21_pciepll_fout_ops,\ + _flags), \ + .index = _idx, \ + } + +static SF21_PCIEPLL_FOUT(pciepll_fout0, 0, 0); +static SF21_PCIEPLL_FOUT(pciepll_fout1, 1, 0); +static SF21_PCIEPLL_FOUT(pciepll_fout2, 2, 0); +static SF21_PCIEPLL_FOUT(pciepll_fout3, 3, 0); + +struct sf21_clk_muxdiv { + struct sf_clk_common common; + u16 en; + u8 mux_reg; + u8 mux_offs; + u8 div_reg; + u8 div_offs; +}; + +#define CRM_CLK_SEL(_x) ((_x) * 4 + 0x80) +#define CLK_SEL1_PLL_TEST GENMASK(6, 4) +#define CRM_CLK_EN 0x8c +#define CRM_CLK_DIV(_x) ((_x) * 4 + 0x94) +#define CRM_CLK_DIV_MASK GENMASK(7, 0) + +static unsigned long sf21_muxdiv_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + ulong div_reg = CRM_CLK_DIV(priv->div_reg); + u16 div_offs = priv->div_offs; + u16 div_val = (sf_readl(cmn_priv, div_reg) >> div_offs) & + CRM_CLK_DIV_MASK; + div_val += 1; + return parent_rate / div_val; +} + +static int sf21_muxdiv_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned int div; + + if (!req->rate) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(req->best_parent_rate, req->rate); + if (!div) + div = 1; + else if (div > CRM_CLK_DIV_MASK + 1) + div = CRM_CLK_DIV_MASK + 1; + + req->rate = req->best_parent_rate / div; + return 0; +} + +static int sf21_muxdiv_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + ulong div_reg = CRM_CLK_DIV(priv->div_reg); + u16 div_offs = priv->div_offs; + unsigned long flags; + unsigned int div; + + if (!rate) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if (div < 1) + div = 1; + else if (div > CRM_CLK_DIV_MASK + 1) + div = CRM_CLK_DIV_MASK + 1; + div -= 1; + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, div_reg, CRM_CLK_DIV_MASK << div_offs, + div << div_offs); + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_DIV); + sf_writel(cmn_priv, CFG_LOAD, 0); + spin_unlock_irqrestore(cmn_priv->lock, flags); + return 0; +} + +static int sf21_muxdiv_enable(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + unsigned long flags; + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, CRM_CLK_EN, 0, BIT(priv->en)); + /* + * Clock divider value load only happens when the clock is running. + * Pulse the CFG_LOAD_DIV so that set_rate() which happened + * before enable() is applied. + */ + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_DIV); + sf_writel(cmn_priv, CFG_LOAD, 0); + spin_unlock_irqrestore(cmn_priv->lock, flags); + return 0; +} + +static void sf21_muxdiv_disable(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + unsigned long flags; + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, CRM_CLK_EN, BIT(priv->en), 0); + spin_unlock_irqrestore(cmn_priv->lock, flags); +} + +static int sf21_muxdiv_is_enabled(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + u32 reg_val = sf_readl(cmn_priv, CRM_CLK_EN); + + return reg_val & (BIT(priv->en)) ? 1 : 0; +} + +static u8 sf21_muxdiv_get_parent(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + ulong mux_reg = CRM_CLK_SEL(priv->mux_reg); + u16 mux_offs = priv->mux_offs; + u32 reg_val = sf_readl(cmn_priv, mux_reg); + + return reg_val & BIT(mux_offs) ? 1 : 0; +} + +static int sf21_muxdiv_set_parent(struct clk_hw *hw, u8 index) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + struct sf21_clk_muxdiv *priv = + container_of(cmn_priv, struct sf21_clk_muxdiv, common); + ulong mux_reg = CRM_CLK_SEL(priv->mux_reg); + u16 mux_offs = priv->mux_offs; + unsigned long flags; + + spin_lock_irqsave(cmn_priv->lock, flags); + if (index) + sf_rmw(cmn_priv, mux_reg, 0, BIT(mux_offs)); + else + sf_rmw(cmn_priv, mux_reg, BIT(mux_offs), 0); + + spin_unlock_irqrestore(cmn_priv->lock, flags); + return 0; +} + +static const struct clk_ops sf21_clk_muxdiv_ops = { + .enable = sf21_muxdiv_enable, + .disable = sf21_muxdiv_disable, + .is_enabled = sf21_muxdiv_is_enabled, + .recalc_rate = sf21_muxdiv_recalc_rate, + .determine_rate = sf21_muxdiv_determine_rate, + .set_rate = sf21_muxdiv_set_rate, + .get_parent = sf21_muxdiv_get_parent, + .set_parent = sf21_muxdiv_set_parent, +}; + +#define SF21_MUXDIV(_name, _parents, _mux_reg, _mux_offs, _div_reg, \ + _div_offs, _en, _flags) \ + struct sf21_clk_muxdiv _name = { \ + .common.hw.init = CLK_HW_INIT_PARENTS_HW( \ + #_name, _parents, &sf21_clk_muxdiv_ops, _flags), \ + .en = _en, \ + .mux_reg = _mux_reg, \ + .mux_offs = _mux_offs, \ + .div_reg = _div_reg, \ + .div_offs = _div_offs, \ + } + +static const struct clk_hw *clk_periph_parents[] = { + &cmnpll_postdiv.hw, + &ddrpll_postdiv.hw, +}; + +static const struct clk_hw *clk_ddr_parents[] = { + &ddrpll_postdiv.hw, + &cmnpll_postdiv.hw, +}; + +static const struct clk_hw *clk_gmac_usb_parents[] = { + &cmnpll_vco.hw, + &ddrpll_postdiv.hw, +}; + +static SF21_MUXDIV(muxdiv_cpu, clk_periph_parents, 0, 1, 0, 0, 0, + CLK_IGNORE_UNUSED); +static SF21_MUXDIV(muxdiv_pic, clk_periph_parents, 0, 3, 3, 16, 1, + CLK_IGNORE_UNUSED); +static SF21_MUXDIV(muxdiv_axi, clk_periph_parents, 0, 5, 0, 8, 2, + CLK_IS_CRITICAL); +static SF21_MUXDIV(muxdiv_ahb, clk_periph_parents, 0, 7, 0, 16, 3, + CLK_IS_CRITICAL); +static SF21_MUXDIV(muxdiv_apb, clk_periph_parents, 0, 9, 0, 24, 4, + CLK_IS_CRITICAL); +static SF21_MUXDIV(muxdiv_uart, clk_periph_parents, 0, 11, 1, 0, 5, 0); +static SF21_MUXDIV(muxdiv_iram, clk_periph_parents, 0, 13, 1, 8, 6, 0); +static SF21_MUXDIV(muxdiv_npu, clk_periph_parents, 0, 17, 1, 24, 8, 0); +static SF21_MUXDIV(muxdiv_ddrphy, clk_ddr_parents, 0, 19, 2, 0, 9, + CLK_IS_CRITICAL); +static SF21_MUXDIV(muxdiv_ddr_bypass, clk_ddr_parents, 0, 21, 3, 0, 10, + CLK_IS_CRITICAL); +static SF21_MUXDIV(muxdiv_ethtsu, clk_periph_parents, 0, 25, 2, 16, 12, + 0); +static SF21_MUXDIV(muxdiv_gmac_byp_ref, clk_gmac_usb_parents, 0, 27, 2, + 24, 13, 0); +static SF21_MUXDIV(muxdiv_usb, clk_gmac_usb_parents, 1, 1, 1, 16, 24, 0); +static SF21_MUXDIV(muxdiv_usbphy, clk_gmac_usb_parents, 1, 3, 2, 8, 25, + 0); +static SF21_MUXDIV(muxdiv_serdes_csr, clk_periph_parents, 1, 15, 5, 0, + 20, 0); +static SF21_MUXDIV(muxdiv_crypt_csr, clk_periph_parents, 1, 17, 5, 8, + 21, 0); +static SF21_MUXDIV(muxdiv_crypt_app, clk_periph_parents, 1, 19, 5, 16, + 22, 0); +static SF21_MUXDIV(muxdiv_irom, clk_periph_parents, 1, 21, 5, 24, 23, + CLK_IS_CRITICAL); + +static int sf21_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + req->rate = req->best_parent_rate; + return 0; +} + +static const struct clk_ops sf21_clk_mux_ops = { + .get_parent = sf21_muxdiv_get_parent, + .set_parent = sf21_muxdiv_set_parent, + .determine_rate = sf21_mux_determine_rate, +}; + +#define SF21_MUX(_name, _parents, _mux_reg, _mux_offs, _flags) \ + struct sf21_clk_muxdiv _name = { \ + .common.hw.init = CLK_HW_INIT_PARENTS_DATA( \ + #_name, _parents, &sf21_clk_mux_ops, _flags), \ + .en = 0, \ + .mux_reg = _mux_reg, \ + .mux_offs = _mux_offs, \ + .div_reg = 0, \ + .div_offs = 0, \ + } + +static const struct clk_parent_data clk_boot_parents[] = { + { .hw = &muxdiv_irom.common.hw }, + { .fw_name = "xin25m" }, +}; + +static SF21_MUX(mux_boot, clk_boot_parents, 0, 30, CLK_IS_CRITICAL); + +static const struct clk_ops sf21_clk_div_ops = { + .recalc_rate = sf21_muxdiv_recalc_rate, + .determine_rate = sf21_muxdiv_determine_rate, + .set_rate = sf21_muxdiv_set_rate, +}; + +#define SF21_DIV(_name, _parent, _div_reg, _div_offs, _flags) \ + struct sf21_clk_muxdiv _name = { \ + .common.hw.init = CLK_HW_INIT_FW_NAME( \ + #_name, _parent, &sf21_clk_div_ops, _flags), \ + .en = 0, \ + .mux_reg = 0, \ + .mux_offs = 0, \ + .div_reg = _div_reg, \ + .div_offs = _div_offs, \ + } + +static SF21_DIV(div_pvt, "xin25m", 3, 8, 0); + +static const struct clk_hw *clk_pll_test_parents[] = { + &cmnpll_postdiv.hw, + &ddrpll_postdiv.hw, + &pciepll_fout3.common.hw, +}; + +static u8 sf21_pll_test_get_parent(struct clk_hw *hw) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + u32 reg_val = sf_readl(cmn_priv, CRM_CLK_SEL(1)); + u8 parent = FIELD_GET(CLK_SEL1_PLL_TEST, reg_val); + + if (parent >= ARRAY_SIZE(clk_pll_test_parents)) + return 0; + + return parent; +} + +static int sf21_pll_test_set_parent(struct clk_hw *hw, u8 index) +{ + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); + unsigned long flags; + + if (index >= ARRAY_SIZE(clk_pll_test_parents)) + return -EINVAL; + + spin_lock_irqsave(cmn_priv->lock, flags); + sf_rmw(cmn_priv, CRM_CLK_SEL(1), CLK_SEL1_PLL_TEST, + FIELD_PREP(CLK_SEL1_PLL_TEST, index)); + spin_unlock_irqrestore(cmn_priv->lock, flags); + return 0; +} + +static const struct clk_ops sf21_clk_pll_test_ops = { + .recalc_rate = sf21_muxdiv_recalc_rate, + .determine_rate = sf21_muxdiv_determine_rate, + .set_rate = sf21_muxdiv_set_rate, + .get_parent = sf21_pll_test_get_parent, + .set_parent = sf21_pll_test_set_parent, +}; + +static struct sf21_clk_muxdiv muxdiv_pll_test = { + .common.hw.init = CLK_HW_INIT_PARENTS_HW("muxdiv_pll_test", + clk_pll_test_parents, + &sf21_clk_pll_test_ops, 0), + .en = 0, + .mux_reg = 0, + .mux_offs = 0, + .div_reg = 3, + .div_offs = 24, +}; + +static const struct clk_ops sf21_clk_gate_ops = { + .enable = sf21_muxdiv_enable, + .disable = sf21_muxdiv_disable, + .is_enabled = sf21_muxdiv_is_enabled, +}; + +#define SF21_GATE(_name, _parents, _en, _flags) \ + struct sf21_clk_muxdiv _name = { \ + .common.hw.init = CLK_HW_INIT_PARENTS_HW( \ + #_name, _parents, &sf21_clk_gate_ops, _flags), \ + .en = _en, \ + .mux_reg = 0, \ + .mux_offs = 0, \ + .div_reg = 0, \ + .div_offs = 0, \ + } + +static const struct clk_hw *clk_pcie_parents[] = { + &pciepll_fout1.common.hw, +}; + +static SF21_GATE(pcie_refclk_n, clk_pcie_parents, 15, 0); +static SF21_GATE(pcie_refclk_p, clk_pcie_parents, 16, 0); + +static struct clk_hw_onecell_data sf21_hw_clks = { + .num = SF21_CLK_MAX, + .hws = { + [SF21_CLK_CMNPLL_VCO] = &cmnpll_vco.hw, + [SF21_CLK_CMNPLL_POSTDIV] = &cmnpll_postdiv.hw, + [SF21_CLK_DDRPLL_POSTDIV] = &ddrpll_postdiv.hw, + [SF21_CLK_PCIEPLL_VCO] = &pciepll_vco.hw, + [SF21_CLK_PCIEPLL_FOUT0] = &pciepll_fout0.common.hw, + [SF21_CLK_PCIEPLL_FOUT1] = &pciepll_fout1.common.hw, + [SF21_CLK_PCIEPLL_FOUT2] = &pciepll_fout2.common.hw, + [SF21_CLK_PCIEPLL_FOUT3] = &pciepll_fout3.common.hw, + [SF21_CLK_CPU] = &muxdiv_cpu.common.hw, + [SF21_CLK_PIC] = &muxdiv_pic.common.hw, + [SF21_CLK_AXI] = &muxdiv_axi.common.hw, + [SF21_CLK_AHB] = &muxdiv_ahb.common.hw, + [SF21_CLK_APB] = &muxdiv_apb.common.hw, + [SF21_CLK_UART] = &muxdiv_uart.common.hw, + [SF21_CLK_IRAM] = &muxdiv_iram.common.hw, + [SF21_CLK_NPU] = &muxdiv_npu.common.hw, + [SF21_CLK_DDRPHY_REF] = &muxdiv_ddrphy.common.hw, + [SF21_CLK_DDR_BYPASS] = &muxdiv_ddr_bypass.common.hw, + [SF21_CLK_ETHTSU] = &muxdiv_ethtsu.common.hw, + [SF21_CLK_GMAC_BYP_REF] = &muxdiv_gmac_byp_ref.common.hw, + [SF21_CLK_USB] = &muxdiv_usb.common.hw, + [SF21_CLK_USBPHY] = &muxdiv_usbphy.common.hw, + [SF21_CLK_SERDES_CSR] = &muxdiv_serdes_csr.common.hw, + [SF21_CLK_CRYPT_CSR] = &muxdiv_crypt_csr.common.hw, + [SF21_CLK_CRYPT_APP] = &muxdiv_crypt_app.common.hw, + [SF21_CLK_IROM] = &muxdiv_irom.common.hw, + [SF21_CLK_BOOT] = &mux_boot.common.hw, + [SF21_CLK_PVT] = &div_pvt.common.hw, + [SF21_CLK_PLL_TEST] = &muxdiv_pll_test.common.hw, + [SF21_CLK_PCIE_REFN] = &pcie_refclk_n.common.hw, + [SF21_CLK_PCIE_REFP] = &pcie_refclk_p.common.hw, + } +}; + +struct sf21_clk_ctrl { + void __iomem *base; + /* Serializes register RMW sequences shared by clocks and resets. */ + spinlock_t lock; + struct reset_controller_dev rcdev; + const u32 *reset_bits; + unsigned int nr_resets; +}; + +#define SF21_SOFT_RESET 0xc0 + +static const u32 sf21_topcrm_reset_bits[] = { + [SF21_RESET_GIC] = BIT(1), + [SF21_RESET_AXI] = BIT(2), + [SF21_RESET_AHB] = BIT(3), + [SF21_RESET_APB] = BIT(4), + [SF21_RESET_IRAM] = BIT(5), + [SF21_RESET_NPU] = BIT(7), + [SF21_RESET_DDR_CTL] = BIT(8), + [SF21_RESET_DDR_PHY] = BIT(9), + [SF21_RESET_DDR_PWR_OK_IN] = BIT(10), + [SF21_RESET_DDR_CTL_APB] = BIT(11), + [SF21_RESET_DDR_PHY_APB] = BIT(12), + [SF21_RESET_USB] = BIT(19), + [SF21_RESET_PVT] = BIT(23), + [SF21_RESET_SERDES_CSR] = BIT(24), + [SF21_RESET_CRYPT_CSR] = BIT(28), + [SF21_RESET_CRYPT_APP] = BIT(29), + [SF21_RESET_NPU2DDR_ASYNCBRIDGE] = BIT(30), + [SF21_RESET_IROM] = BIT(31), +}; + +static inline struct sf21_clk_ctrl * +rcdev_to_sf21_topcrm(struct reset_controller_dev *rcdev) +{ + return container_of(rcdev, struct sf21_clk_ctrl, rcdev); +} + +static int sf21_topcrm_reset_update(struct reset_controller_dev *rcdev, + unsigned long id, bool assert) +{ + struct sf21_clk_ctrl *ctrl = rcdev_to_sf21_topcrm(rcdev); + u32 bit = ctrl->reset_bits[id]; + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&ctrl->lock, flags); + reg = readl(ctrl->base + SF21_SOFT_RESET); + if (assert) + reg &= ~bit; + else + reg |= bit; + writel(reg, ctrl->base + SF21_SOFT_RESET); + spin_unlock_irqrestore(&ctrl->lock, flags); + + return 0; +} + +static int sf21_topcrm_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sf21_topcrm_reset_update(rcdev, id, true); +} + +static int sf21_topcrm_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + return sf21_topcrm_reset_update(rcdev, id, false); +} + +static int sf21_topcrm_reset_status(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct sf21_clk_ctrl *ctrl = rcdev_to_sf21_topcrm(rcdev); + u32 bit = ctrl->reset_bits[id]; + + return !(readl(ctrl->base + SF21_SOFT_RESET) & bit); +} + +static const struct reset_control_ops sf21_topcrm_reset_ops = { + .assert = sf21_topcrm_reset_assert, + .deassert = sf21_topcrm_reset_deassert, + .status = sf21_topcrm_reset_status, +}; + +static int sf21_topcrm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sf21_clk_ctrl *ctrl; + int i, ret; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->base)) + return dev_err_probe(dev, PTR_ERR(ctrl->base), + "failed to map resources\n"); + + spin_lock_init(&ctrl->lock); + + for (i = 0; i < sf21_hw_clks.num; i++) { + struct clk_hw *hw = sf21_hw_clks.hws[i]; + struct sf_clk_common *common; + + if (!hw) + continue; + common = hw_to_sf_clk_common(hw); + common->base = ctrl->base; + common->lock = &ctrl->lock; + ret = devm_clk_hw_register(dev, hw); + if (ret) + return dev_err_probe(dev, ret, + "failed to register clock %d\n", + i); + } + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, + &sf21_hw_clks); + if (ret) + return dev_err_probe(dev, ret, "failed to add hw provider\n"); + + ctrl->reset_bits = sf21_topcrm_reset_bits; + ctrl->nr_resets = ARRAY_SIZE(sf21_topcrm_reset_bits); + ctrl->rcdev.owner = THIS_MODULE; + ctrl->rcdev.nr_resets = ctrl->nr_resets; + ctrl->rcdev.ops = &sf21_topcrm_reset_ops; + ctrl->rcdev.of_node = dev->of_node; + ctrl->rcdev.of_reset_n_cells = 1; + + ret = devm_reset_controller_register(dev, &ctrl->rcdev); + if (ret) + return dev_err_probe(dev, ret, + "failed to register reset controller\n"); + + return 0; +} + +static const struct of_device_id sf21_topcrm_dt_ids[] = { + { .compatible = "siflower,sf21-topcrm" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, sf21_topcrm_dt_ids); + +static struct platform_driver sf21_topcrm_driver = { + .probe = sf21_topcrm_probe, + .driver = { + .name = "sf21-topcrm", + .suppress_bind_attrs = true, + .of_match_table = sf21_topcrm_dt_ids, + }, +}; +module_platform_driver(sf21_topcrm_driver); + +MODULE_AUTHOR("Chuanhong Guo <gch981213@gmail.com>"); +MODULE_DESCRIPTION("driver for Siflower SF21 top clock and reset module"); +MODULE_LICENSE("GPL"); -- 2.54.0 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 4/4] clk: add support for siflower sf21-topcrm 2026-05-17 14:12 ` [PATCH 4/4] clk: add support for siflower sf21-topcrm Chuanhong Guo @ 2026-05-17 15:09 ` sashiko-bot 2026-05-18 14:12 ` Chuanhong Guo 2026-05-18 12:21 ` Yao Zi 1 sibling, 1 reply; 22+ messages in thread From: sashiko-bot @ 2026-05-17 15:09 UTC (permalink / raw) To: Chuanhong Guo; +Cc: conor+dt, robh, devicetree Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: - [Critical] Static initialization of the flexible array in `sf21_hw_clks` creates a critical out-of-bounds memory access vulnerability. - [High] Lock nesting violation on PREEMPT_RT due to `spinlock_t` usage inside `.enable`/`.disable` callbacks. - [High] Global static clock definition structures are modified with per-device dynamically allocated pointers. - [High] Missing `select RATIONAL` in Kconfig leads to linker errors. - [Medium] Integer truncation in PLL rate calculations causes precision loss. - [Medium] Silent acceptance of invalid or unsupported soft reset IDs. -- commit 83e3e7ec9410a85a7d5912f8b3485089ca10bdce Author: Chuanhong Guo <gch981213@gmail.com> clk: add support for siflower sf21-topcrm This commit adds a driver for the toplevel clock and reset controller found on Siflower SF21A6826/SF21H8898 SoCs. It includes support for multiple PLLs, mux/gate/divider blocks, and a reset register. > diff --git a/drivers/clk/siflower/Kconfig b/drivers/clk/siflower/Kconfig > new file mode 100644 > index 0000000000000..03cbfbdbdb8db > --- /dev/null > +++ b/drivers/clk/siflower/Kconfig > @@ -0,0 +1,22 @@ [ ... ] > +config CLK_SF21_TOPCRM > + tristate "Clock driver for Siflower SF21 toplevel clock & reset module" > + depends on ARCH_SIFLOWER || COMPILE_TEST > + default ARCH_SIFLOWER > + select RESET_CONTROLLER Should this select RATIONAL? The driver calls rational_best_approximation() in rate calculations, which could lead to linker errors if RATIONAL is not enabled during a COMPILE_TEST build. [ ... ] > diff --git a/drivers/clk/siflower/clk-sf21-topcrm.c b/drivers/clk/siflower/clk-sf21-topcrm.c > new file mode 100644 > index 0000000000000..7d4c5e370d6d2 > --- /dev/null > +++ b/drivers/clk/siflower/clk-sf21-topcrm.c > @@ -0,0 +1,1053 @@ [ ... ] > +struct sf_clk_common { > + void __iomem *base; > + /* Serializes register RMW sequences shared by clocks and resets. */ > + spinlock_t *lock; Could this lock nesting cause issues on PREEMPT_RT? The Common Clock Framework invokes .enable and .disable callbacks while holding its own enable_lock, which is a raw_spinlock_t. Acquiring a spinlock_t inside a raw spinlock critical section is a lock nesting violation and can lead to a scheduling-while-atomic panic on PREEMPT_RT kernels. Does this hardware lock need to be a raw_spinlock_t? > + struct clk_hw hw; > +}; [ ... ] > +static unsigned long sf21_cmnpll_vco_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); > + u32 cfg = sf_readl(priv, PLL_CMN_CFG1); > + unsigned long refdiv = FIELD_GET(PLL_CMN_REFDIV, cfg); > + unsigned long fbdiv = FIELD_GET(PLL_CMN_FBDIV, cfg); > + > + if (!refdiv || !fbdiv) > + return 0; > + > + return (parent_rate / refdiv) * fbdiv; Could this integer division truncate the remainder and cause precision loss in the reported frequency? This pattern also appears in other rate calculation functions like sf21_pciepll_vco_recalc_rate and sf21_ddrpll_postdiv_recalc_rate. Would it be better to use 64-bit math such as div_u64((u64)parent_rate * fbdiv, refdiv) or mult_frac()? > +} [ ... ] > +static struct clk_hw_onecell_data sf21_hw_clks = { > + .num = SF21_CLK_MAX, > + .hws = { > + [SF21_CLK_CMNPLL_VCO] = &cmnpll_vco.hw, [ ... ] > + [SF21_CLK_PCIE_REFN] = &pcie_refclk_n.common.hw, > + [SF21_CLK_PCIE_REFP] = &pcie_refclk_p.common.hw, > + } > +}; If SF21_CLK_MAX is larger than the number of items explicitly initialized in this array, could this lead to an out-of-bounds memory read? When statically initialized, the compiler only allocates space up to the highest explicitly initialized index (SF21_CLK_PCIE_REFP). If the loop in the probe function reads up to sf21_hw_clks.num, it could read past the end of the allocated structure. [ ... ] > +struct sf21_clk_ctrl { > + void __iomem *base; > + /* Serializes register RMW sequences shared by clocks and resets. */ > + spinlock_t lock; > + struct reset_controller_dev rcdev; > + const u32 *reset_bits; > + unsigned int nr_resets; > +}; [ ... ] > +static int sf21_topcrm_reset_update(struct reset_controller_dev *rcdev, > + unsigned long id, bool assert) > +{ > + struct sf21_clk_ctrl *ctrl = rcdev_to_sf21_topcrm(rcdev); > + u32 bit = ctrl->reset_bits[id]; Can this accept invalid or unsupported reset IDs? Since the sf21_topcrm_reset_bits array has unpopulated gaps, bit will evaluate to 0 for unsupported IDs. If this happens, the code blindly performs reg &= ~0 or reg |= 0 and returns success without throwing an error. Should it verify that the bit is valid and return -EINVAL if not? > + unsigned long flags; > + u32 reg; > + > + spin_lock_irqsave(&ctrl->lock, flags); > + reg = readl(ctrl->base + SF21_SOFT_RESET); > + if (assert) > + reg &= ~bit; > + else > + reg |= bit; > + writel(reg, ctrl->base + SF21_SOFT_RESET); > + spin_unlock_irqrestore(&ctrl->lock, flags); > + > + return 0; > +} [ ... ] > +static int sf21_topcrm_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct sf21_clk_ctrl *ctrl; > + int i, ret; > + > + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); > + if (!ctrl) > + return -ENOMEM; > + > + ctrl->base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(ctrl->base)) > + return dev_err_probe(dev, PTR_ERR(ctrl->base), > + "failed to map resources\n"); > + > + spin_lock_init(&ctrl->lock); > + > + for (i = 0; i < sf21_hw_clks.num; i++) { > + struct clk_hw *hw = sf21_hw_clks.hws[i]; > + struct sf_clk_common *common; > + > + if (!hw) > + continue; > + common = hw_to_sf_clk_common(hw); > + common->base = ctrl->base; > + common->lock = &ctrl->lock; Could modifying global static clock definitions with per-device pointers lead to a use-after-free? If probe defers or fails later, ctrl is freed by devres, leaving the global clock objects pointing to dangling memory. Subsequent probes could then encounter corrupted pointers. > + ret = devm_clk_hw_register(dev, hw); > + if (ret) > + return dev_err_probe(dev, ret, > + "failed to register clock %d\n", > + i); > + } [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260517-sf21-topcrm-v1-0-438f2e0513ff@gmail.com?part=4 ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 4/4] clk: add support for siflower sf21-topcrm 2026-05-17 15:09 ` sashiko-bot @ 2026-05-18 14:12 ` Chuanhong Guo 0 siblings, 0 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-18 14:12 UTC (permalink / raw) To: sashiko-reviews; +Cc: conor+dt, robh, devicetree On Sun, May 17, 2026 at 11:09 PM <sashiko-bot@kernel.org> wrote: > > Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider: > - [Critical] Static initialization of the flexible array in `sf21_hw_clks` creates a critical out-of-bounds memory access vulnerability. > - [High] Lock nesting violation on PREEMPT_RT due to `spinlock_t` usage inside `.enable`/`.disable` callbacks. > - [High] Global static clock definition structures are modified with per-device dynamically allocated pointers. > - [High] Missing `select RATIONAL` in Kconfig leads to linker errors. > - [Medium] Integer truncation in PLL rate calculations causes precision loss. > - [Medium] Silent acceptance of invalid or unsupported soft reset IDs. > -- > > commit 83e3e7ec9410a85a7d5912f8b3485089ca10bdce > Author: Chuanhong Guo <gch981213@gmail.com> > > clk: add support for siflower sf21-topcrm > > This commit adds a driver for the toplevel clock and reset controller > found on Siflower SF21A6826/SF21H8898 SoCs. It includes support for > multiple PLLs, mux/gate/divider blocks, and a reset register. > > > diff --git a/drivers/clk/siflower/Kconfig b/drivers/clk/siflower/Kconfig > > new file mode 100644 > > index 0000000000000..03cbfbdbdb8db > > --- /dev/null > > +++ b/drivers/clk/siflower/Kconfig > > @@ -0,0 +1,22 @@ > [ ... ] > > +config CLK_SF21_TOPCRM > > + tristate "Clock driver for Siflower SF21 toplevel clock & reset module" > > + depends on ARCH_SIFLOWER || COMPILE_TEST > > + default ARCH_SIFLOWER > > + select RESET_CONTROLLER > > Should this select RATIONAL? The driver calls rational_best_approximation() > in rate calculations, which could lead to linker errors if RATIONAL is not > enabled during a COMPILE_TEST build. Correct. I'll fix this in v2. > > [ ... ] > > diff --git a/drivers/clk/siflower/clk-sf21-topcrm.c b/drivers/clk/siflower/clk-sf21-topcrm.c > > new file mode 100644 > > index 0000000000000..7d4c5e370d6d2 > > --- /dev/null > > +++ b/drivers/clk/siflower/clk-sf21-topcrm.c > > @@ -0,0 +1,1053 @@ > [ ... ] > > +struct sf_clk_common { > > + void __iomem *base; > > + /* Serializes register RMW sequences shared by clocks and resets. */ > > + spinlock_t *lock; > > Could this lock nesting cause issues on PREEMPT_RT? > > The Common Clock Framework invokes .enable and .disable callbacks while > holding its own enable_lock, which is a raw_spinlock_t. Acquiring a > spinlock_t inside a raw spinlock critical section is a lock nesting > violation and can lead to a scheduling-while-atomic panic on PREEMPT_RT > kernels. > > Does this hardware lock need to be a raw_spinlock_t? I'll change this in v2. I'll probably also split this into clock_lock and reset_lock since the latter doesn't need raw_spinlock_t. > > > + struct clk_hw hw; > > +}; > [ ... ] > > +static unsigned long sf21_cmnpll_vco_recalc_rate(struct clk_hw *hw, > > + unsigned long parent_rate) > > +{ > > + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); > > + u32 cfg = sf_readl(priv, PLL_CMN_CFG1); > > + unsigned long refdiv = FIELD_GET(PLL_CMN_REFDIV, cfg); > > + unsigned long fbdiv = FIELD_GET(PLL_CMN_FBDIV, cfg); > > + > > + if (!refdiv || !fbdiv) > > + return 0; > > + > > + return (parent_rate / refdiv) * fbdiv; > > Could this integer division truncate the remainder and cause precision > loss in the reported frequency? > > This pattern also appears in other rate calculation functions like > sf21_pciepll_vco_recalc_rate and sf21_ddrpll_postdiv_recalc_rate. Would it > be better to use 64-bit math such as div_u64((u64)parent_rate * fbdiv, > refdiv) or mult_frac()? Sure. I'll use div_u64 in v2. > > > +} > [ ... ] > > +static struct clk_hw_onecell_data sf21_hw_clks = { > > + .num = SF21_CLK_MAX, > > + .hws = { > > + [SF21_CLK_CMNPLL_VCO] = &cmnpll_vco.hw, > [ ... ] > > + [SF21_CLK_PCIE_REFN] = &pcie_refclk_n.common.hw, > > + [SF21_CLK_PCIE_REFP] = &pcie_refclk_p.common.hw, > > + } > > +}; > > If SF21_CLK_MAX is larger than the number of items explicitly initialized > in this array, could this lead to an out-of-bounds memory read? > > When statically initialized, the compiler only allocates space up to the > highest explicitly initialized index (SF21_CLK_PCIE_REFP). If the loop in > the probe function reads up to sf21_hw_clks.num, it could read past the end > of the allocated structure. SF21_CLK_MAX = SF21_CLK_PCIE_REFP + 1 so this won't happen. > > [ ... ] > > +struct sf21_clk_ctrl { > > + void __iomem *base; > > + /* Serializes register RMW sequences shared by clocks and resets. */ > > + spinlock_t lock; > > + struct reset_controller_dev rcdev; > > + const u32 *reset_bits; > > + unsigned int nr_resets; > > +}; > [ ... ] > > +static int sf21_topcrm_reset_update(struct reset_controller_dev *rcdev, > > + unsigned long id, bool assert) > > +{ > > + struct sf21_clk_ctrl *ctrl = rcdev_to_sf21_topcrm(rcdev); > > + u32 bit = ctrl->reset_bits[id]; > > Can this accept invalid or unsupported reset IDs? Since the > sf21_topcrm_reset_bits array has unpopulated gaps, bit will evaluate to 0 > for unsupported IDs. > > If this happens, the code blindly performs reg &= ~0 or reg |= 0 and > returns success without throwing an error. Should it verify that the bit > is valid and return -EINVAL if not? This should have been guarded by fwnode_reset_simple_xlate check on nr_resets. > > > + unsigned long flags; > > + u32 reg; > > + > > + spin_lock_irqsave(&ctrl->lock, flags); > > + reg = readl(ctrl->base + SF21_SOFT_RESET); > > + if (assert) > > + reg &= ~bit; > > + else > > + reg |= bit; > > + writel(reg, ctrl->base + SF21_SOFT_RESET); > > + spin_unlock_irqrestore(&ctrl->lock, flags); > > + > > + return 0; > > +} > [ ... ] > > +static int sf21_topcrm_probe(struct platform_device *pdev) > > +{ > > + struct device *dev = &pdev->dev; > > + struct sf21_clk_ctrl *ctrl; > > + int i, ret; > > + > > + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); > > + if (!ctrl) > > + return -ENOMEM; > > + > > + ctrl->base = devm_platform_ioremap_resource(pdev, 0); > > + if (IS_ERR(ctrl->base)) > > + return dev_err_probe(dev, PTR_ERR(ctrl->base), > > + "failed to map resources\n"); > > + > > + spin_lock_init(&ctrl->lock); > > + > > + for (i = 0; i < sf21_hw_clks.num; i++) { > > + struct clk_hw *hw = sf21_hw_clks.hws[i]; > > + struct sf_clk_common *common; > > + > > + if (!hw) > > + continue; > > + common = hw_to_sf_clk_common(hw); > > + common->base = ctrl->base; > > + common->lock = &ctrl->lock; > > Could modifying global static clock definitions with per-device pointers > lead to a use-after-free? > > If probe defers or fails later, ctrl is freed by devres, leaving the > global clock objects pointing to dangling memory. Subsequent probes could > then encounter corrupted pointers. No. All the clock registers are called with devm_ variant. Before freeing ctrl, all the clocks should already have been unregistered and shouldn't be touched by any code until it's registered next time. -- Regards, Chuanhong Guo ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 4/4] clk: add support for siflower sf21-topcrm 2026-05-17 14:12 ` [PATCH 4/4] clk: add support for siflower sf21-topcrm Chuanhong Guo 2026-05-17 15:09 ` sashiko-bot @ 2026-05-18 12:21 ` Yao Zi 2026-05-18 13:34 ` Chuanhong Guo 1 sibling, 1 reply; 22+ messages in thread From: Yao Zi @ 2026-05-18 12:21 UTC (permalink / raw) To: Chuanhong Guo, Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel Cc: linux-riscv, linux-kernel, linux-clk, devicetree On Sun, May 17, 2026 at 10:12:58PM +0800, Chuanhong Guo wrote: > This commit adds a driver for the Toplevel clock and reset controller > found on Siflower SF21A6826/SF21H8898 SoCs. > This block contains control for 3 PLLs, several clock mux/gate/divider > blocks, and a reset register for on-chip peripherals. It would be better if you could split out the reset code into drivers/reset, and initialize the reset controller as an auxiliary device, like what has been done for SpacemiT platform (drivers/{clock,reset}/spacemit) and AMLogic platform (drivers/clock/meson/axg-audio.c and drivers/reset/amlogic/reset-meson-aux.c). I am neither clock nor reset maintainer, thus this only serves as a suggestion, with which it ends up in better code structure. > There are also two registers for enabling PCIE clock output in this > block. They aren't covered by this patch because I can't test those > without a PCIE driver. These will be added with the PCIE driver s/PCIE/PCIe/g which is the formal spelling. > patchset later after I get that working. > > Signed-off-by: Chuanhong Guo <gch981213@gmail.com> > --- > drivers/clk/Kconfig | 1 + > drivers/clk/Makefile | 1 + > drivers/clk/siflower/Kconfig | 22 + > drivers/clk/siflower/Makefile | 1 + > drivers/clk/siflower/clk-sf21-topcrm.c | 1053 ++++++++++++++++++++++++++++++++ > 5 files changed, 1078 insertions(+) ... > diff --git a/drivers/clk/siflower/clk-sf21-topcrm.c b/drivers/clk/siflower/clk-sf21-topcrm.c > new file mode 100644 > index 000000000000..7d4c5e370d6d > --- /dev/null > +++ b/drivers/clk/siflower/clk-sf21-topcrm.c > @@ -0,0 +1,1053 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <linux/kernel.h> > +#include <linux/of.h> > +#include <linux/slab.h> > +#include <linux/clk-provider.h> > +#include <linux/bitfield.h> > +#include <linux/io.h> > +#include <linux/iopoll.h> > +#include <linux/rational.h> > +#include <linux/module.h> > +#include <linux/reset-controller.h> > +#include <linux/spinlock.h> > +#include <linux/platform_device.h> > +#include <dt-bindings/clock/siflower,sf21-topcrm.h> Consider sorting the headers? ... > +static int sf21_cmnpll_vco_determine_rate(struct clk_hw *hw, > + struct clk_rate_request *req) > +{ > + unsigned long fbdiv, refdiv; > + > + rational_best_approximation(req->rate, req->best_parent_rate, > + BIT(PLL_CMN_FBDIV_BITS) - 1, > + BIT(PLL_CMN_REFDIV_BITS) - 1, &fbdiv, FIELD_MAX(PLL_CMN_{FBDIV,REFDIV}_BITS}) would be simpler, with which it should be possible to get avoid of PLL_CMN_*_BITS. > + &refdiv); > + if (!refdiv || !fbdiv) > + return -EINVAL; > + > + req->rate = (req->best_parent_rate / refdiv) * fbdiv; > + > + return 0; > +} > + > +static int sf21_cmnpll_vco_set_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long parent_rate) > +{ > + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); > + unsigned long flags; > + unsigned long fbdiv, refdiv; > + u32 val; > + int ret; > + > + rational_best_approximation(rate, parent_rate, > + BIT(PLL_CMN_FBDIV_BITS) - 1, > + BIT(PLL_CMN_REFDIV_BITS) - 1, &fbdiv, > + &refdiv); > + if (!refdiv || !fbdiv) > + return -EINVAL; > + > + spin_lock_irqsave(priv->lock, flags); guard(spinlock_irqsave)(priv->lock) might simplify the code (especially the error handling path in this function), this applies as well for other places where spin_lock_irqsave() involves. > + > + sf_rmw(priv, PLL_CMN_CFG1, PLL_CMN_REFDIV | PLL_CMN_FBDIV | PLL_CMN_PD, > + FIELD_PREP(PLL_CMN_REFDIV, refdiv) | > + FIELD_PREP(PLL_CMN_FBDIV, fbdiv)); > + sf_writel(priv, CFG_LOAD, CFG_LOAD_CMN_PLL); > + sf_writel(priv, CFG_LOAD, 0); > + > + ret = readl_poll_timeout_atomic(priv->base + PLL_CMN_LOCK, val, val & 1, > + 0, PLL_LOCK_TIMEOUT_US); > + if (ret) > + goto out_unlock; > + > +out_unlock: > + spin_unlock_irqrestore(priv->lock, flags); > + return ret; > +} ... > +static unsigned long sf21_dualdiv_round_rate(unsigned long rate, > + unsigned long parent_rate, > + unsigned int range, > + unsigned int *diva, > + unsigned int *divb) > +{ > + unsigned int div = DIV_ROUND_CLOSEST(parent_rate, rate); > + unsigned int best_diff, da, db, cur_div, cur_diff; > + > + if (div <= 1) { > + *diva = 1; > + *divb = 1; > + return parent_rate; > + } > + > + best_diff = div - 1; > + *diva = 1; > + *divb = 1; > + > + for (da = 1; da <= range; da++) { > + db = DIV_ROUND_CLOSEST(div, da); > + if (db > da) > + db = da; > + > + cur_div = da * db; > + if (div > cur_div) > + cur_diff = div - cur_div; > + else > + cur_diff = cur_div - div; > + > + if (cur_diff < best_diff) { > + best_diff = cur_diff; > + *diva = da; > + *divb = db; > + } > + if (cur_diff == 0) > + break; > + } > + > + return parent_rate / *diva / *divb; > +} > + > +static int sf21_cmnpll_postdiv_determine_rate(struct clk_hw *hw, > + struct clk_rate_request *req) > +{ > + unsigned int diva, divb; > + > + if (!req->rate) > + return -EINVAL; > + > + req->rate = sf21_dualdiv_round_rate(req->rate, req->best_parent_rate, > + 7, &diva, &divb); > + > + return 0; > +} > + > +static int sf21_cmnpll_postdiv_set_rate(struct clk_hw *hw, > + unsigned long rate, > + unsigned long parent_rate) > +{ > + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); > + unsigned int diva, divb; > + unsigned long flags; > + > + if (!rate) > + return -EINVAL; > + > + sf21_dualdiv_round_rate(rate, parent_rate, 7, &diva, &divb); > + > + spin_lock_irqsave(priv->lock, flags); > + sf_rmw(priv, PLL_CMN_CFG1, PLL_CMN_POSTDIV1 | PLL_CMN_POSTDIV2, > + FIELD_PREP(PLL_CMN_POSTDIV1, diva) | > + FIELD_PREP(PLL_CMN_POSTDIV2, divb)); > + sf_writel(priv, CFG_LOAD, CFG_LOAD_CMN_PLL); > + sf_writel(priv, CFG_LOAD, 0); > + spin_unlock_irqrestore(priv->lock, flags); > + return 0; > +} > + > +static unsigned long > +sf21_cmnpll_postdiv_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); > + u32 cfg = sf_readl(priv, PLL_CMN_CFG1); > + unsigned long div1 = FIELD_GET(PLL_CMN_POSTDIV1, cfg); > + unsigned long div2 = FIELD_GET(PLL_CMN_POSTDIV2, cfg); > + > + if (!div1 || !div2) > + return 0; > + > + return parent_rate / div1 / div2; > +} > + > +static const struct clk_ops sf21_cmnpll_postdiv_ops = { > + .recalc_rate = sf21_cmnpll_postdiv_recalc_rate, > + .determine_rate = sf21_cmnpll_postdiv_determine_rate, > + .set_rate = sf21_cmnpll_postdiv_set_rate, > +}; > + > +static struct sf_clk_common cmnpll_postdiv = { > + .hw.init = CLK_HW_INIT_HW("cmnpll_postdiv", &cmnpll_vco.hw, > + &sf21_cmnpll_postdiv_ops, 0), > +}; ... > +static int sf21_pciepll_fout_determine_rate(struct clk_hw *hw, > + struct clk_rate_request *req) > +{ > + unsigned int diva, divb; > + > + if (!req->rate) > + return -EINVAL; > + > + req->rate = sf21_dualdiv_round_rate(req->rate, req->best_parent_rate, > + 8, &diva, &divb); cmnpll_postdiv works in a very similar way to pciepll_fout. As you could see in the code, they both contain two divisors to configure, and their rates could all be calculated through sf21_dualdiv_round_rate(), > + return 0; > +} ... > +static const struct clk_ops sf21_pciepll_fout_ops = { > + .enable = sf21_pciepll_fout_enable, > + .disable = sf21_pciepll_fout_disable, > + .is_enabled = sf21_pciepll_fout_is_enabled, Besides field/register offsets, the only difference I could tell between cmnpll_postdiv and pciepll_fout is that pciepll_fout clocks could be gated. Would it be a good idea to describe the gating function separately as a clock, and merge the common part of pciepll_fout and cmnpll_postdiv? In which way you could save a lot of mostly duplicated code. > + .recalc_rate = sf21_pciepll_fout_recalc_rate, > + .determine_rate = sf21_pciepll_fout_determine_rate, > + .set_rate = sf21_pciepll_fout_set_rate, > +}; ... > +struct sf21_clk_muxdiv { > + struct sf_clk_common common; > + u16 en; > + u8 mux_reg; > + u8 mux_offs; > + u8 div_reg; > + u8 div_offs; > +}; > + > +#define CRM_CLK_SEL(_x) ((_x) * 4 + 0x80) > +#define CLK_SEL1_PLL_TEST GENMASK(6, 4) > +#define CRM_CLK_EN 0x8c > +#define CRM_CLK_DIV(_x) ((_x) * 4 + 0x94) > +#define CRM_CLK_DIV_MASK GENMASK(7, 0) > + > +static unsigned long sf21_muxdiv_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + ulong div_reg = CRM_CLK_DIV(priv->div_reg); > + u16 div_offs = priv->div_offs; > + u16 div_val = (sf_readl(cmn_priv, div_reg) >> div_offs) & > + CRM_CLK_DIV_MASK; > + div_val += 1; > + return parent_rate / div_val; > +} > + > +static int sf21_muxdiv_determine_rate(struct clk_hw *hw, > + struct clk_rate_request *req) > +{ > + unsigned int div; > + > + if (!req->rate) > + return -EINVAL; > + > + div = DIV_ROUND_CLOSEST(req->best_parent_rate, req->rate); > + if (!div) > + div = 1; > + else if (div > CRM_CLK_DIV_MASK + 1) > + div = CRM_CLK_DIV_MASK + 1; > + > + req->rate = req->best_parent_rate / div; > + return 0; > +} > + > +static int sf21_muxdiv_set_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long parent_rate) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + ulong div_reg = CRM_CLK_DIV(priv->div_reg); > + u16 div_offs = priv->div_offs; > + unsigned long flags; > + unsigned int div; > + > + if (!rate) > + return -EINVAL; > + > + div = DIV_ROUND_CLOSEST(parent_rate, rate); > + if (div < 1) > + div = 1; > + else if (div > CRM_CLK_DIV_MASK + 1) > + div = CRM_CLK_DIV_MASK + 1; > + div -= 1; > + > + spin_lock_irqsave(cmn_priv->lock, flags); > + sf_rmw(cmn_priv, div_reg, CRM_CLK_DIV_MASK << div_offs, > + div << div_offs); > + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_DIV); > + sf_writel(cmn_priv, CFG_LOAD, 0); > + spin_unlock_irqrestore(cmn_priv->lock, flags); > + return 0; > +} > + > +static int sf21_muxdiv_enable(struct clk_hw *hw) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + unsigned long flags; > + > + spin_lock_irqsave(cmn_priv->lock, flags); > + sf_rmw(cmn_priv, CRM_CLK_EN, 0, BIT(priv->en)); > + /* > + * Clock divider value load only happens when the clock is running. > + * Pulse the CFG_LOAD_DIV so that set_rate() which happened > + * before enable() is applied. > + */ > + sf_writel(cmn_priv, CFG_LOAD, CFG_LOAD_DIV); > + sf_writel(cmn_priv, CFG_LOAD, 0); > + spin_unlock_irqrestore(cmn_priv->lock, flags); > + return 0; > +} > + > +static void sf21_muxdiv_disable(struct clk_hw *hw) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + unsigned long flags; > + > + spin_lock_irqsave(cmn_priv->lock, flags); > + sf_rmw(cmn_priv, CRM_CLK_EN, BIT(priv->en), 0); > + spin_unlock_irqrestore(cmn_priv->lock, flags); > +} > + > +static int sf21_muxdiv_is_enabled(struct clk_hw *hw) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + u32 reg_val = sf_readl(cmn_priv, CRM_CLK_EN); > + > + return reg_val & (BIT(priv->en)) ? 1 : 0; > +} > + > +static u8 sf21_muxdiv_get_parent(struct clk_hw *hw) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + ulong mux_reg = CRM_CLK_SEL(priv->mux_reg); > + u16 mux_offs = priv->mux_offs; > + u32 reg_val = sf_readl(cmn_priv, mux_reg); > + > + return reg_val & BIT(mux_offs) ? 1 : 0; > +} > + > +static int sf21_muxdiv_set_parent(struct clk_hw *hw, u8 index) > +{ > + struct sf_clk_common *cmn_priv = hw_to_sf_clk_common(hw); > + struct sf21_clk_muxdiv *priv = > + container_of(cmn_priv, struct sf21_clk_muxdiv, common); > + ulong mux_reg = CRM_CLK_SEL(priv->mux_reg); > + u16 mux_offs = priv->mux_offs; > + unsigned long flags; > + > + spin_lock_irqsave(cmn_priv->lock, flags); > + if (index) > + sf_rmw(cmn_priv, mux_reg, 0, BIT(mux_offs)); > + else > + sf_rmw(cmn_priv, mux_reg, BIT(mux_offs), 0); > + > + spin_unlock_irqrestore(cmn_priv->lock, flags); > + return 0; > +} I believe besides the divider reloading part, clk_mux_ops, clk_divider_ops, and clk_gate_ops have already provided the logic you implemented here. So it might be a better option to composite them together to implement your clocks instead of building from scratch. > +static const struct clk_ops sf21_clk_muxdiv_ops = { > + .enable = sf21_muxdiv_enable, > + .disable = sf21_muxdiv_disable, > + .is_enabled = sf21_muxdiv_is_enabled, > + .recalc_rate = sf21_muxdiv_recalc_rate, > + .determine_rate = sf21_muxdiv_determine_rate, > + .set_rate = sf21_muxdiv_set_rate, > + .get_parent = sf21_muxdiv_get_parent, > + .set_parent = sf21_muxdiv_set_parent, > +}; ... > +static SF21_MUXDIV(muxdiv_cpu, clk_periph_parents, 0, 1, 0, 0, 0, > + CLK_IGNORE_UNUSED); If it supplies the CPU core, shouldn't it be CLK_IS_CRITICAL instead? > +static SF21_MUXDIV(muxdiv_pic, clk_periph_parents, 0, 3, 3, 16, 1, > + CLK_IGNORE_UNUSED); Do you have any information about purpose of the clock and why it's marked as CLK_IGNORE_UNUSED? It's better to have a comment explaining this since it's not very obvious... Best regards, Yao Zi ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 4/4] clk: add support for siflower sf21-topcrm 2026-05-18 12:21 ` Yao Zi @ 2026-05-18 13:34 ` Chuanhong Guo 0 siblings, 0 replies; 22+ messages in thread From: Chuanhong Guo @ 2026-05-18 13:34 UTC (permalink / raw) To: Yao Zi Cc: Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Michael Turquette, Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Philipp Zabel, linux-riscv, linux-kernel, linux-clk, devicetree Hi! On Mon, May 18, 2026 at 8:22 PM Yao Zi <me@ziyao.cc> wrote: > > On Sun, May 17, 2026 at 10:12:58PM +0800, Chuanhong Guo wrote: > > This commit adds a driver for the Toplevel clock and reset controller > > found on Siflower SF21A6826/SF21H8898 SoCs. > > This block contains control for 3 PLLs, several clock mux/gate/divider > > blocks, and a reset register for on-chip peripherals. > > It would be better if you could split out the reset code into > drivers/reset, and initialize the reset controller as an auxiliary > device, like what has been done for SpacemiT platform > (drivers/{clock,reset}/spacemit) and AMLogic platform > (drivers/clock/meson/axg-audio.c and > drivers/reset/amlogic/reset-meson-aux.c). It seems pretty common for these kinds of combined clock and reset blocks to have a single driver. (There are 32 "select RESET_CONTROLLER" in drivers/clk.) I deliberately chose to merge the clock and reset together since it's named "clock and reset module" in the datasheet and is a single block for both clock and reset on this chip. I think syscon is usually used for those registers with a bunch of miscellaneous control fields instead. > > I am neither clock nor reset maintainer, thus this only serves as > a suggestion, with which it ends up in better code structure. > > > There are also two registers for enabling PCIE clock output in this > > block. They aren't covered by this patch because I can't test those > > without a PCIE driver. These will be added with the PCIE driver > > s/PCIE/PCIe/g which is the formal spelling. Sure. I'll fix this in v2. > > > patchset later after I get that working. > > > > Signed-off-by: Chuanhong Guo <gch981213@gmail.com> > > --- > > drivers/clk/Kconfig | 1 + > > drivers/clk/Makefile | 1 + > > drivers/clk/siflower/Kconfig | 22 + > > drivers/clk/siflower/Makefile | 1 + > > drivers/clk/siflower/clk-sf21-topcrm.c | 1053 ++++++++++++++++++++++++++++++++ > > 5 files changed, 1078 insertions(+) > > ... > > > diff --git a/drivers/clk/siflower/clk-sf21-topcrm.c b/drivers/clk/siflower/clk-sf21-topcrm.c > > new file mode 100644 > > index 000000000000..7d4c5e370d6d > > --- /dev/null > > +++ b/drivers/clk/siflower/clk-sf21-topcrm.c > > @@ -0,0 +1,1053 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > + > > +#include <linux/kernel.h> > > +#include <linux/of.h> > > +#include <linux/slab.h> > > +#include <linux/clk-provider.h> > > +#include <linux/bitfield.h> > > +#include <linux/io.h> > > +#include <linux/iopoll.h> > > +#include <linux/rational.h> > > +#include <linux/module.h> > > +#include <linux/reset-controller.h> > > +#include <linux/spinlock.h> > > +#include <linux/platform_device.h> > > +#include <dt-bindings/clock/siflower,sf21-topcrm.h> > > Consider sorting the headers? OK. > > ... > > > +static int sf21_cmnpll_vco_determine_rate(struct clk_hw *hw, > > + struct clk_rate_request *req) > > +{ > > + unsigned long fbdiv, refdiv; > > + > > + rational_best_approximation(req->rate, req->best_parent_rate, > > + BIT(PLL_CMN_FBDIV_BITS) - 1, > > + BIT(PLL_CMN_REFDIV_BITS) - 1, &fbdiv, > > FIELD_MAX(PLL_CMN_{FBDIV,REFDIV}_BITS}) would be simpler, with which it > should be possible to get avoid of PLL_CMN_*_BITS. Oh, I didn't know this is a thing. I'll change it in v2. > > > + &refdiv); > > + if (!refdiv || !fbdiv) > > + return -EINVAL; > > + > > + req->rate = (req->best_parent_rate / refdiv) * fbdiv; > > + > > + return 0; > > +} > > + > > +static int sf21_cmnpll_vco_set_rate(struct clk_hw *hw, unsigned long rate, > > + unsigned long parent_rate) > > +{ > > + struct sf_clk_common *priv = hw_to_sf_clk_common(hw); > > + unsigned long flags; > > + unsigned long fbdiv, refdiv; > > + u32 val; > > + int ret; > > + > > + rational_best_approximation(rate, parent_rate, > > + BIT(PLL_CMN_FBDIV_BITS) - 1, > > + BIT(PLL_CMN_REFDIV_BITS) - 1, &fbdiv, > > + &refdiv); > > + if (!refdiv || !fbdiv) > > + return -EINVAL; > > + > > + spin_lock_irqsave(priv->lock, flags); > > guard(spinlock_irqsave)(priv->lock) > > might simplify the code (especially the error handling path in this > function), this applies as well for other places where > spin_lock_irqsave() involves. Oh, that macro is cool. I'll use it in v2. > > [...] > > Besides field/register offsets, the only difference I could tell between > cmnpll_postdiv and pciepll_fout is that pciepll_fout clocks could be > gated. > > Would it be a good idea to describe the gating function separately as a > clock, and merge the common part of pciepll_fout and cmnpll_postdiv? In > which way you could save a lot of mostly duplicated code. I don't think so. Since the majority of the existing code are register operations, and the register bit layout are completely different, there isn't much to share between these two PLLs. Writing a struct to describe both register layouts together seems unnecessarily complicated and harder to read. BTW the CMNPLL and PCIEPLL are actually different hardware. The former is an integer PLL while the latter actually supports fractional operations. I didn't add support for the fractional part due to the lack of use cases and documentation. PCIE and GMAC clocks are fed by this PLL and require exact clock frequencies which can be achieved using only the integer mode. > > > + .recalc_rate = sf21_pciepll_fout_recalc_rate, > > + .determine_rate = sf21_pciepll_fout_determine_rate, > > + .set_rate = sf21_pciepll_fout_set_rate, > > +}; > [...] > > + > > + spin_lock_irqsave(cmn_priv->lock, flags); > > + if (index) > > + sf_rmw(cmn_priv, mux_reg, 0, BIT(mux_offs)); > > + else > > + sf_rmw(cmn_priv, mux_reg, BIT(mux_offs), 0); > > + > > + spin_unlock_irqrestore(cmn_priv->lock, flags); > > + return 0; > > +} > > I believe besides the divider reloading part, clk_mux_ops, > clk_divider_ops, and clk_gate_ops have already provided the logic > you implemented here. So it might be a better option to composite them > together to implement your clocks instead of building from scratch. The divider reloading is the exact reason I chose to not compose them with the function you mentioned. The reloading bit need to be set on both clock divider change and clock enabling, because the clock divider loading only happens when the clock is running. Since I already have to write two of the three parts you mentioned, trying to reuse clk_mux_ops doesn't seem to reduce the code complexity here. It's just trading get_parent and set_parent call with one more level in the clock tree for every clock and more code to wire them together in the probe function. > > > +static const struct clk_ops sf21_clk_muxdiv_ops = { > > + .enable = sf21_muxdiv_enable, > > + .disable = sf21_muxdiv_disable, > > + .is_enabled = sf21_muxdiv_is_enabled, > > + .recalc_rate = sf21_muxdiv_recalc_rate, > > + .determine_rate = sf21_muxdiv_determine_rate, > > + .set_rate = sf21_muxdiv_set_rate, > > + .get_parent = sf21_muxdiv_get_parent, > > + .set_parent = sf21_muxdiv_set_parent, > > +}; > > ... > > > +static SF21_MUXDIV(muxdiv_cpu, clk_periph_parents, 0, 1, 0, 0, 0, > > + CLK_IGNORE_UNUSED); > > If it supplies the CPU core, shouldn't it be CLK_IS_CRITICAL instead? > > > +static SF21_MUXDIV(muxdiv_pic, clk_periph_parents, 0, 3, 3, 16, 1, > > + CLK_IGNORE_UNUSED); > > Do you have any information about purpose of the clock and why it's > marked as CLK_IGNORE_UNUSED? It's better to have a comment explaining > this since it's not very obvious... This one is Platform Interrupt Controller and it feeds PLIC + CLINT in the C908 core complex. I copied these from the vendor driver. I'll make them CLK_IS_CRITICAL in v2. -- Regards, Chuanhong Guo ^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-05-18 14:17 UTC | newest] Thread overview: 22+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-05-17 14:12 [PATCH 0/4] clk: add Siflower SF21 topcrm support Chuanhong Guo 2026-05-17 14:12 ` [PATCH 1/4] riscv: add Siflower RISC-V SoC family Kconfig support Chuanhong Guo 2026-05-17 14:31 ` sashiko-bot 2026-05-17 20:46 ` Conor Dooley 2026-05-18 14:17 ` Chuanhong Guo 2026-05-17 14:12 ` [PATCH 2/4] dt-bindings: clock: add binding header for sf21-topcrm Chuanhong Guo 2026-05-17 14:36 ` sashiko-bot 2026-05-17 20:47 ` Conor Dooley 2026-05-17 20:51 ` Conor Dooley 2026-05-18 11:42 ` Chuanhong Guo 2026-05-18 11:04 ` Yao Zi 2026-05-18 11:43 ` Chuanhong Guo 2026-05-17 14:12 ` [PATCH 3/4] dt-bindings: clock: add doc for Siflower sf21-topcrm Chuanhong Guo 2026-05-17 15:35 ` Rob Herring (Arm) 2026-05-17 20:50 ` Conor Dooley 2026-05-18 12:12 ` Chuanhong Guo 2026-05-18 12:28 ` Yao Zi 2026-05-17 14:12 ` [PATCH 4/4] clk: add support for siflower sf21-topcrm Chuanhong Guo 2026-05-17 15:09 ` sashiko-bot 2026-05-18 14:12 ` Chuanhong Guo 2026-05-18 12:21 ` Yao Zi 2026-05-18 13:34 ` Chuanhong Guo
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox