public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 0/3] Add Tenstorrent Atlantis Clock/Reset Controller
@ 2026-03-03 17:36 Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 1/3] dt-bindings: clk: tenstorrent: Add tenstorrent,atlantis-prcm-rcpu Anirudh Srinivasan
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Anirudh Srinivasan @ 2026-03-03 17:36 UTC (permalink / raw)
  To: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Anirudh Srinivasan,
	Philipp Zabel
  Cc: linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, bmasney, Krzysztof Kozlowski

This series adds support for a multifunctional register block
called PRCM in the Tenstorrent Atlantis SoC, whose main functionality
is to serve clocks and resets. This block is instantiated multiple
times in the SoC, with each block covering clock/resets from a
different subsystem. This series also adds a driver that covers clocks
and resets from the RCPU subsystem, which covers most low speed IO
interfaces found in the chip. The reset controller is implemented as
an auxiliary device of the clock controller and shares the same regmap
as it.

The first commit adds bindings documenting the PRCM block, along with
clock and reset indices. The second commit adds the reset controller
driver. The third commit adds the clock controller driver, and the reset
controller is created as an auxdev of it.

Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>

---
Changes in v7:
- Rebase on v7.0-rc1
- Added Rb tags for Patch 1 from Krzysztof and Rb+Ab tags for Patch 2
  from Philipp
- Addressed comments from Brian on on making clk_ops const static and
  use of !! operator for non zero value checking in atlantis_clk_gate_is_enabled
- Removed multiple error messages that would be printed on failure to
  register a clk
- Corrected clock-gating bits for some of the PLLs and adjusted
  pll_is_enabled function accordingly
- Link to v6: https://lore.kernel.org/r/20260216-atlantis-clocks-v6-0-cb46d6a59c73@oss.tenstorrent.com

Changes in v6:
- Renamed bindings for PRCM module from "tenstorrent,atlantis-prcm" to
  "tenstorrent,atlantis-prcm-rcpu" so that filename matches compatible
- Updated reset driver to fetch parent device's regmap instead of using
  auxdev structs
- Moved reset register offsets from include/soc/tenstorrent header to
  reset driver
- In light of above 2 changes, dropped dropped Rb tags for reset
  controller patch from Philipp Zabel
- Moved clock register offsets offsets from include/soc/tenstorrent
  header to clock driver
- Removed shared header file in include/soc/tenstorrent
- Remove boilerplate code for initializing and register reset auxdev in
  clock driver and create it using devm_auxiliary_device_create instead
- Removed struct atlantis_prcm and it's occurrences as it is actually
  not used anywhere
- Fixed an incorrect use of boolean && instead of bitwise & in pll ops
  in clock driver
- Link to v5: https://lore.kernel.org/r/20260202-atlantis-clocks-v5-0-0922e43acaba@oss.tenstorrent.com

Changes in v5:
- Replaced to_atlantis_*/hw_to_atlantis_* macros with static inline functions in clk and reset driver
- Add Rb tags from Philipp Zabel
- Link to v4: https://lore.kernel.org/r/20260130-atlantis-clocks-v4-0-46de52d52924@oss.tenstorrent.com

Changes in v4:
- Dropped !rcpu prcms and custom property used by them. Will be added
  later with rest of clock tree
- Link to v3: https://lore.kernel.org/r/20260126-atlantis-clocks-v3-0-b016135551b7@oss.tenstorrent.com

Changes in v3:
- Fixed warnings reported by kernel test bot
- Addressed comments on reset driver bit set/unset value
- Changed auxdev macros to use inline functions
- Renamed bindings and clock driver to use block's name of "PRCM" rather
  than syscon
- Link to v2: https://lore.kernel.org/r/20260122-atlantis-clocks-v2-0-c66371639e66@oss.tenstorrent.com

Changes in v2:
- Improve the documentation about the syscon block in bindings
- Implemented all clks using custom ops
- Removed custom lock/lock handling functions for regmap
- Addressed comments on header file ordering, newlines and typos
- Removed code for mux parent setting
- Squashed down multiple commits that added reset/auxdev separately
- Link to v1: https://lore.kernel.org/r/20260115-atlantis-clocks-v1-0-7356e671f28b@oss.tenstorrent.com

---
Anirudh Srinivasan (3):
      dt-bindings: clk: tenstorrent: Add tenstorrent,atlantis-prcm-rcpu
      reset: tenstorrent: Add reset controller for Atlantis
      clk: tenstorrent: Add Atlantis clock controller driver

 .../clock/tenstorrent,atlantis-prcm-rcpu.yaml      |  54 ++
 MAINTAINERS                                        |   4 +
 drivers/clk/Kconfig                                |   1 +
 drivers/clk/Makefile                               |   1 +
 drivers/clk/tenstorrent/Kconfig                    |  14 +
 drivers/clk/tenstorrent/Makefile                   |   3 +
 drivers/clk/tenstorrent/atlantis-prcm.c            | 893 +++++++++++++++++++++
 drivers/reset/Kconfig                              |  11 +
 drivers/reset/Makefile                             |   1 +
 drivers/reset/reset-tenstorrent-atlantis.c         | 173 ++++
 .../clock/tenstorrent,atlantis-prcm-rcpu.h         | 103 +++
 11 files changed, 1258 insertions(+)
---
base-commit: 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f
change-id: 20260112-atlantis-clocks-f090c190b86d

Best regards,
-- 
Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>


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

* [PATCH v7 1/3] dt-bindings: clk: tenstorrent: Add tenstorrent,atlantis-prcm-rcpu
  2026-03-03 17:36 [PATCH v7 0/3] Add Tenstorrent Atlantis Clock/Reset Controller Anirudh Srinivasan
@ 2026-03-03 17:36 ` Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 2/3] reset: tenstorrent: Add reset controller for Atlantis Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver Anirudh Srinivasan
  2 siblings, 0 replies; 8+ messages in thread
From: Anirudh Srinivasan @ 2026-03-03 17:36 UTC (permalink / raw)
  To: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Anirudh Srinivasan,
	Philipp Zabel
  Cc: linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, bmasney, Krzysztof Kozlowski

Document bindings for Tenstorrent Atlantis PRCM that manages clocks
and resets. This block is instantiated multiple times in the SoC.
This commit documents the clocks from the RCPU PRCM block.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
---
 .../clock/tenstorrent,atlantis-prcm-rcpu.yaml      |  54 +++++++++++
 MAINTAINERS                                        |   2 +
 .../clock/tenstorrent,atlantis-prcm-rcpu.h         | 103 +++++++++++++++++++++
 3 files changed, 159 insertions(+)

diff --git a/Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml b/Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml
new file mode 100644
index 000000000000..7fa16526efce
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/clock/tenstorrent,atlantis-prcm-rcpu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Tenstorrent Atlantis PRCM (Power, Reset, Clock Management) Module
+
+maintainers:
+  - Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
+
+description:
+  Multifunctional register block found in Tenstorrent Atlantis SoC whose main
+  function is to control clocks and resets. This block is instantiated multiple
+  times in the SoC, each block controls clock and resets for a different
+  subsystem. RCPU prcm serves low speed IO interfaces.
+
+properties:
+  compatible:
+    enum:
+      - tenstorrent,atlantis-prcm-rcpu
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  "#clock-cells":
+    const: 1
+    description:
+      See <dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h> for valid indices.
+
+  "#reset-cells":
+    const: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - "#clock-cells"
+  - "#reset-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    clock-controller@a8000000 {
+      compatible = "tenstorrent,atlantis-prcm-rcpu";
+      reg = <0xa8000000 0x10000>;
+      clocks = <&osc_24m>;
+      #clock-cells = <1>;
+      #reset-cells = <1>;
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 55af015174a5..40c179c8de1e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22809,8 +22809,10 @@ M:	Joel Stanley <jms@oss.tenstorrent.com>
 L:	linux-riscv@lists.infradead.org
 S:	Maintained
 T:	git https://github.com/tenstorrent/linux.git
+F:	Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml
 F:	Documentation/devicetree/bindings/riscv/tenstorrent.yaml
 F:	arch/riscv/boot/dts/tenstorrent/
+F:	include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h
 
 RISC-V THEAD SoC SUPPORT
 M:	Drew Fustini <fustini@kernel.org>
diff --git a/include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h b/include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h
new file mode 100644
index 000000000000..c1c875e016f8
--- /dev/null
+++ b/include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Tenstorrent Atlantis PRCM Clock and Reset Indices
+ *
+ * Copyright (c) 2026 Tenstorrent
+ */
+
+#ifndef _DT_BINDINGS_ATLANTIS_PRCM_RCPU_H
+#define _DT_BINDINGS_ATLANTIS_PRCM_RCPU_H
+
+/*
+ * RCPU Domain Clock IDs
+ */
+#define CLK_RCPU_PLL		0
+#define CLK_RCPU_ROOT		1
+#define CLK_RCPU_DIV2		2
+#define CLK_RCPU_DIV4		3
+#define CLK_RCPU_RTC		4
+#define CLK_SMNDMA0_ACLK	5
+#define CLK_SMNDMA1_ACLK	6
+#define CLK_WDT0_PCLK		7
+#define CLK_WDT1_PCLK		8
+#define CLK_TIMER_PCLK		9
+#define CLK_PVTC_PCLK		10
+#define CLK_PMU_PCLK		11
+#define CLK_MAILBOX_HCLK	12
+#define CLK_SEC_SPACC_HCLK	13
+#define CLK_SEC_OTP_HCLK	14
+#define CLK_TRNG_PCLK		15
+#define CLK_SEC_CRC_HCLK	16
+#define CLK_SMN_HCLK		17
+#define CLK_AHB0_HCLK		18
+#define CLK_SMN_PCLK		19
+#define CLK_SMN_CLK		20
+#define CLK_SCRATCHPAD_CLK	21
+#define CLK_RCPU_CORE_CLK	22
+#define CLK_RCPU_ROM_CLK	23
+#define CLK_OTP_LOAD_CLK	24
+#define CLK_NOC_PLL		25
+#define CLK_NOCC_CLK		26
+#define CLK_NOCC_DIV2		27
+#define CLK_NOCC_DIV4		28
+#define CLK_NOCC_RTC		29
+#define CLK_NOCC_CAN		30
+#define CLK_QSPI_SCLK		31
+#define CLK_QSPI_HCLK		32
+#define CLK_I2C0_PCLK		33
+#define CLK_I2C1_PCLK		34
+#define CLK_I2C2_PCLK		35
+#define CLK_I2C3_PCLK		36
+#define CLK_I2C4_PCLK		37
+#define CLK_UART0_PCLK		38
+#define CLK_UART1_PCLK		39
+#define CLK_UART2_PCLK		40
+#define CLK_UART3_PCLK		41
+#define CLK_UART4_PCLK		42
+#define CLK_SPI0_PCLK		43
+#define CLK_SPI1_PCLK		44
+#define CLK_SPI2_PCLK		45
+#define CLK_SPI3_PCLK		46
+#define CLK_GPIO_PCLK		47
+#define CLK_CAN0_HCLK		48
+#define CLK_CAN0_CLK		49
+#define CLK_CAN1_HCLK		50
+#define CLK_CAN1_CLK		51
+#define CLK_CAN0_TIMER_CLK	52
+#define CLK_CAN1_TIMER_CLK	53
+
+/* RCPU domain reset */
+#define RST_SMNDMA0		0
+#define RST_SMNDMA1		1
+#define RST_WDT0		2
+#define RST_WDT1		3
+#define RST_TMR			4
+#define RST_PVTC		5
+#define RST_PMU			6
+#define RST_MAILBOX		7
+#define RST_SPACC		8
+#define RST_OTP			9
+#define RST_TRNG		10
+#define RST_CRC			11
+#define RST_QSPI		12
+#define RST_I2C0		13
+#define RST_I2C1		14
+#define RST_I2C2		15
+#define RST_I2C3		16
+#define RST_I2C4		17
+#define RST_UART0		18
+#define RST_UART1		19
+#define RST_UART2		20
+#define RST_UART3		21
+#define RST_UART4		22
+#define RST_SPI0		23
+#define RST_SPI1		24
+#define RST_SPI2		25
+#define RST_SPI3		26
+#define RST_GPIO		27
+#define RST_CAN0		28
+#define RST_CAN1		29
+#define RST_I2S0		30
+#define RST_I2S1		31
+
+#endif /* _DT_BINDINGS_ATLANTIS_PRCM_RCPU_H */

-- 
2.43.0


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

* [PATCH v7 2/3] reset: tenstorrent: Add reset controller for Atlantis
  2026-03-03 17:36 [PATCH v7 0/3] Add Tenstorrent Atlantis Clock/Reset Controller Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 1/3] dt-bindings: clk: tenstorrent: Add tenstorrent,atlantis-prcm-rcpu Anirudh Srinivasan
@ 2026-03-03 17:36 ` Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver Anirudh Srinivasan
  2 siblings, 0 replies; 8+ messages in thread
From: Anirudh Srinivasan @ 2026-03-03 17:36 UTC (permalink / raw)
  To: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Anirudh Srinivasan,
	Philipp Zabel
  Cc: linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, bmasney, Krzysztof Kozlowski

Adds Atlantis Reset Controller driver, which shares the same regmap as
prcm ( clock controller).

This version of the reset controller driver covers resets from the RCPU
prcm.

Reviewed-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
---
 MAINTAINERS                                |   1 +
 drivers/reset/Kconfig                      |  11 ++
 drivers/reset/Makefile                     |   1 +
 drivers/reset/reset-tenstorrent-atlantis.c | 173 +++++++++++++++++++++++++++++
 4 files changed, 186 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 40c179c8de1e..493d007d3c65 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22812,6 +22812,7 @@ T:	git https://github.com/tenstorrent/linux.git
 F:	Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml
 F:	Documentation/devicetree/bindings/riscv/tenstorrent.yaml
 F:	arch/riscv/boot/dts/tenstorrent/
+F:	drivers/reset/reset-tenstorrent-atlantis.c
 F:	include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h
 
 RISC-V THEAD SoC SUPPORT
diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index 7ce151f6a7e4..85ee9da809ee 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -315,6 +315,17 @@ config RESET_SUNXI
 	help
 	  This enables the reset driver for Allwinner SoCs.
 
+config RESET_TENSTORRENT_ATLANTIS
+	tristate "Tenstorrent atlantis reset driver"
+	depends on ARCH_TENSTORRENT || COMPILE_TEST
+	select AUXILIARY_BUS
+	default ARCH_TENSTORRENT
+	help
+	  This enables the driver for the reset controller
+	  present in the Tenstorrent Atlantis SoC.
+	  Enable this option to be able to use hardware
+	  resets on Atalantis based systems.
+
 config RESET_TH1520
 	tristate "T-HEAD TH1520 reset controller"
 	depends on ARCH_THEAD || COMPILE_TEST
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index fc0cc99f8514..7c086baeb02a 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o
 obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o
 obj-$(CONFIG_RESET_SUNPLUS) += reset-sunplus.o
 obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o
+obj-$(CONFIG_RESET_TENSTORRENT_ATLANTIS) += reset-tenstorrent-atlantis.o
 obj-$(CONFIG_RESET_TH1520) += reset-th1520.o
 obj-$(CONFIG_RESET_TI_SCI) += reset-ti-sci.o
 obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o
diff --git a/drivers/reset/reset-tenstorrent-atlantis.c b/drivers/reset/reset-tenstorrent-atlantis.c
new file mode 100644
index 000000000000..ab8be52fdd5e
--- /dev/null
+++ b/drivers/reset/reset-tenstorrent-atlantis.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Tenstorrent Atlantis PRCM Reset Driver
+ *
+ * Copyright (c) 2026 Tenstorrent
+ */
+
+#include <dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/reset-controller.h>
+#include <linux/regmap.h>
+
+/* RCPU Reset Register Offsets */
+#define RCPU_BLK_RST_REG 0x001c
+#define LSIO_BLK_RST_REG 0x0020
+#define HSIO_BLK_RST_REG 0x000c
+#define PCIE_SUBS_RST_REG 0x0000
+#define MM_RSTN_REG 0x0014
+
+struct atlantis_reset_data {
+	u8 bit;
+	u16 reg;
+	bool active_low;
+};
+
+struct atlantis_reset_controller_data {
+	const struct atlantis_reset_data *reset_data;
+	size_t count;
+};
+
+struct atlantis_reset_controller {
+	struct reset_controller_dev rcdev;
+	const struct atlantis_reset_controller_data *data;
+	struct regmap *regmap;
+};
+
+static inline struct atlantis_reset_controller *
+to_atlantis_reset_controller(struct reset_controller_dev *rcdev)
+{
+	return container_of(rcdev, struct atlantis_reset_controller, rcdev);
+}
+
+#define RESET_DATA(_reg, _bit, _active_low)                          \
+	{                                                            \
+		.bit = _bit, .reg = _reg, .active_low = _active_low, \
+	}
+
+static const struct atlantis_reset_data atlantis_rcpu_resets[] = {
+	[RST_SMNDMA0]	= RESET_DATA(RCPU_BLK_RST_REG, 0, true),
+	[RST_SMNDMA1]	= RESET_DATA(RCPU_BLK_RST_REG, 1, true),
+	[RST_WDT0]	= RESET_DATA(RCPU_BLK_RST_REG, 2, true),
+	[RST_WDT1]	= RESET_DATA(RCPU_BLK_RST_REG, 3, true),
+	[RST_TMR]	= RESET_DATA(RCPU_BLK_RST_REG, 4, true),
+	[RST_PVTC]	= RESET_DATA(RCPU_BLK_RST_REG, 12, true),
+	[RST_PMU]	= RESET_DATA(RCPU_BLK_RST_REG, 13, true),
+	[RST_MAILBOX]	= RESET_DATA(RCPU_BLK_RST_REG, 14, true),
+	[RST_SPACC]	= RESET_DATA(RCPU_BLK_RST_REG, 26, true),
+	[RST_OTP]	= RESET_DATA(RCPU_BLK_RST_REG, 28, true),
+	[RST_TRNG]	= RESET_DATA(RCPU_BLK_RST_REG, 29, true),
+	[RST_CRC]	= RESET_DATA(RCPU_BLK_RST_REG, 30, true),
+	[RST_QSPI]	= RESET_DATA(LSIO_BLK_RST_REG, 0, true),
+	[RST_I2C0]	= RESET_DATA(LSIO_BLK_RST_REG, 1, true),
+	[RST_I2C1]	= RESET_DATA(LSIO_BLK_RST_REG, 2, true),
+	[RST_I2C2]	= RESET_DATA(LSIO_BLK_RST_REG, 3, true),
+	[RST_I2C3]	= RESET_DATA(LSIO_BLK_RST_REG, 4, true),
+	[RST_I2C4]	= RESET_DATA(LSIO_BLK_RST_REG, 5, true),
+	[RST_UART0]	= RESET_DATA(LSIO_BLK_RST_REG, 6, true),
+	[RST_UART1]	= RESET_DATA(LSIO_BLK_RST_REG, 7, true),
+	[RST_UART2]	= RESET_DATA(LSIO_BLK_RST_REG, 8, true),
+	[RST_UART3]	= RESET_DATA(LSIO_BLK_RST_REG, 9, true),
+	[RST_UART4]	= RESET_DATA(LSIO_BLK_RST_REG, 10, true),
+	[RST_SPI0]	= RESET_DATA(LSIO_BLK_RST_REG, 11, true),
+	[RST_SPI1]	= RESET_DATA(LSIO_BLK_RST_REG, 12, true),
+	[RST_SPI2]	= RESET_DATA(LSIO_BLK_RST_REG, 13, true),
+	[RST_SPI3]	= RESET_DATA(LSIO_BLK_RST_REG, 14, true),
+	[RST_GPIO]	= RESET_DATA(LSIO_BLK_RST_REG, 15, true),
+	[RST_CAN0]	= RESET_DATA(LSIO_BLK_RST_REG, 17, true),
+	[RST_CAN1]	= RESET_DATA(LSIO_BLK_RST_REG, 18, true),
+	[RST_I2S0]	= RESET_DATA(LSIO_BLK_RST_REG, 19, true),
+	[RST_I2S1]	= RESET_DATA(LSIO_BLK_RST_REG, 20, true),
+
+};
+
+static const struct atlantis_reset_controller_data atlantis_rcpu_reset_data = {
+	.reset_data = atlantis_rcpu_resets,
+	.count = ARRAY_SIZE(atlantis_rcpu_resets),
+};
+
+static int atlantis_reset_update(struct reset_controller_dev *rcdev,
+				 unsigned long id, bool assert)
+{
+	unsigned int val;
+	struct atlantis_reset_controller *rst =
+		to_atlantis_reset_controller(rcdev);
+	const struct atlantis_reset_data *data = &rst->data->reset_data[id];
+	unsigned int mask = BIT(data->bit);
+	struct regmap *regmap = rst->regmap;
+
+	if (data->active_low ^ assert)
+		val = mask;
+	else
+		val = 0;
+
+	return regmap_update_bits(regmap, data->reg, mask, val);
+}
+
+static int atlantis_reset_assert(struct reset_controller_dev *rcdev,
+				 unsigned long id)
+{
+	return atlantis_reset_update(rcdev, id, true);
+}
+
+static int atlantis_reset_deassert(struct reset_controller_dev *rcdev,
+				   unsigned long id)
+{
+	return atlantis_reset_update(rcdev, id, false);
+}
+
+static const struct reset_control_ops atlantis_reset_control_ops = {
+	.assert = atlantis_reset_assert,
+	.deassert = atlantis_reset_deassert,
+};
+
+static int
+atlantis_reset_controller_register(struct device *dev,
+				   struct atlantis_reset_controller *controller)
+{
+	struct reset_controller_dev *rcdev = &controller->rcdev;
+
+	rcdev->ops = &atlantis_reset_control_ops;
+	rcdev->owner = THIS_MODULE;
+	rcdev->of_node = dev->of_node;
+	rcdev->nr_resets = controller->data->count;
+
+	return devm_reset_controller_register(dev, &controller->rcdev);
+}
+static int atlantis_reset_probe(struct auxiliary_device *adev,
+				const struct auxiliary_device_id *id)
+{
+	struct atlantis_reset_controller *controller;
+	struct device *dev = &adev->dev;
+	struct regmap *regmap;
+
+	regmap = dev_get_regmap(dev->parent, NULL);
+	if (!regmap)
+		return -ENODEV;
+
+	controller = devm_kzalloc(dev, sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+	controller->data =
+		(const struct atlantis_reset_controller_data *)id->driver_data;
+	controller->regmap = regmap;
+
+	return atlantis_reset_controller_register(dev, controller);
+}
+
+static const struct auxiliary_device_id atlantis_reset_ids[] = {
+	{ .name = "atlantis_prcm.rcpu-reset",
+	  .driver_data = (kernel_ulong_t)&atlantis_rcpu_reset_data },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, atlantis_reset_ids);
+
+static struct auxiliary_driver atlantis_reset_driver = {
+	.probe = atlantis_reset_probe,
+	.id_table = atlantis_reset_ids,
+};
+module_auxiliary_driver(atlantis_reset_driver);
+
+MODULE_AUTHOR("Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>");
+MODULE_DESCRIPTION("Atlantis PRCM reset controller driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0


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

* [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver
  2026-03-03 17:36 [PATCH v7 0/3] Add Tenstorrent Atlantis Clock/Reset Controller Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 1/3] dt-bindings: clk: tenstorrent: Add tenstorrent,atlantis-prcm-rcpu Anirudh Srinivasan
  2026-03-03 17:36 ` [PATCH v7 2/3] reset: tenstorrent: Add reset controller for Atlantis Anirudh Srinivasan
@ 2026-03-03 17:36 ` Anirudh Srinivasan
  2026-03-03 22:32   ` Brian Masney
  2 siblings, 1 reply; 8+ messages in thread
From: Anirudh Srinivasan @ 2026-03-03 17:36 UTC (permalink / raw)
  To: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Anirudh Srinivasan,
	Philipp Zabel
  Cc: linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, bmasney, Krzysztof Kozlowski

Add driver for clock controller in Tenstorrent Atlantis SoC. This version
of the driver covers clocks from RCPU subsystem.

5 types of clocks generated by this controller: PLLs (PLLs
with bypass functionality and an additional Gate clk at output), Shared
Gates (Multiple Gate clks that share an enable bit), standard Muxes,
Dividers and Gates. All clocks are implemented using custom clk ops and
use the regmap interface associated with the syscon. All clocks are derived
from a 24 Mhz oscillator.

The reset controller is also setup as an auxiliary device of the clock
controller.

Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
---
 MAINTAINERS                             |   1 +
 drivers/clk/Kconfig                     |   1 +
 drivers/clk/Makefile                    |   1 +
 drivers/clk/tenstorrent/Kconfig         |  14 +
 drivers/clk/tenstorrent/Makefile        |   3 +
 drivers/clk/tenstorrent/atlantis-prcm.c | 893 ++++++++++++++++++++++++++++++++
 6 files changed, 913 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 493d007d3c65..a7783eb0a7de 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22812,6 +22812,7 @@ T:	git https://github.com/tenstorrent/linux.git
 F:	Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml
 F:	Documentation/devicetree/bindings/riscv/tenstorrent.yaml
 F:	arch/riscv/boot/dts/tenstorrent/
+F:	drivers/clk/tenstorrent/
 F:	drivers/reset/reset-tenstorrent-atlantis.c
 F:	include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h
 
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 3d803b4cf5c1..8cc300b90b5f 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -531,6 +531,7 @@ source "drivers/clk/starfive/Kconfig"
 source "drivers/clk/sunxi/Kconfig"
 source "drivers/clk/sunxi-ng/Kconfig"
 source "drivers/clk/tegra/Kconfig"
+source "drivers/clk/tenstorrent/Kconfig"
 source "drivers/clk/thead/Kconfig"
 source "drivers/clk/stm32/Kconfig"
 source "drivers/clk/ti/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f7bce3951a30..f52cf3ac64fc 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -155,6 +155,7 @@ obj-y					+= starfive/
 obj-$(CONFIG_ARCH_SUNXI)		+= sunxi/
 obj-y					+= sunxi-ng/
 obj-$(CONFIG_ARCH_TEGRA)		+= tegra/
+obj-y					+= tenstorrent/
 obj-$(CONFIG_ARCH_THEAD)		+= thead/
 obj-y					+= ti/
 obj-$(CONFIG_CLK_UNIPHIER)		+= uniphier/
diff --git a/drivers/clk/tenstorrent/Kconfig b/drivers/clk/tenstorrent/Kconfig
new file mode 100644
index 000000000000..9d4391eeeae0
--- /dev/null
+++ b/drivers/clk/tenstorrent/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config TENSTORRENT_ATLANTIS_PRCM
+	tristate "Support for Tenstorrent Atlantis PRCM Clock Controller"
+	depends on ARCH_TENSTORRENT || COMPILE_TEST
+	default ARCH_TENSTORRENT
+	select REGMAP_MMIO
+	select AUXILIARY_BUS
+	select MFD_SYSCON
+	help
+	  Say yes here to support the different clock
+	  controllers found in the Tenstorrent Atlantis SoC.
+	  This includes the clocks from the RCPU, HSIO, MMIO
+	  and PCIE domain.
diff --git a/drivers/clk/tenstorrent/Makefile b/drivers/clk/tenstorrent/Makefile
new file mode 100644
index 000000000000..95d87bac7bf5
--- /dev/null
+++ b/drivers/clk/tenstorrent/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_TENSTORRENT_ATLANTIS_PRCM)	+= atlantis-prcm.o
diff --git a/drivers/clk/tenstorrent/atlantis-prcm.c b/drivers/clk/tenstorrent/atlantis-prcm.c
new file mode 100644
index 000000000000..b0a8e3526901
--- /dev/null
+++ b/drivers/clk/tenstorrent/atlantis-prcm.c
@@ -0,0 +1,893 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tenstorrent Atlantis PRCM Clock Driver
+ *
+ * Copyright (c) 2026 Tenstorrent
+ */
+
+#include <dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+/* RCPU Clock Register Offsets */
+#define RCPU_PLL_CFG_REG	0x0000
+#define RCPU_NOCC_PLL_CFG_REG	0x0004
+#define RCPU_NOCC_CLK_CFG_REG	0x0008
+#define RCPU_DIV_CFG_REG	0x000C
+#define RCPU_BLK_CG_REG		0x0014
+#define LSIO_BLK_CG_REG		0x0018
+#define PLL_RCPU_EN_REG		0x011c
+#define PLL_NOCC_EN_REG		0x0120
+#define BUS_CG_REG		0x01FC
+
+/* PLL Bit Definitions */
+#define PLL_CFG_EN_BIT		BIT(0)
+#define PLL_CFG_BYPASS_BIT	BIT(1)
+#define PLL_CFG_REFDIV_MASK	GENMASK(7, 2)
+#define PLL_CFG_REFDIV_SHIFT	2
+#define PLL_CFG_POSTDIV1_MASK	GENMASK(10, 8)
+#define PLL_CFG_POSTDIV1_SHIFT	8
+#define PLL_CFG_POSTDIV2_MASK	GENMASK(13, 11)
+#define PLL_CFG_POSTDIV2_SHIFT	11
+#define PLL_CFG_FBDIV_MASK	GENMASK(25, 14)
+#define PLL_CFG_FBDIV_SHIFT	14
+#define PLL_CFG_LKDT_BIT	BIT(30)
+#define PLL_CFG_LOCK_BIT	BIT(31)
+#define PLL_LOCK_TIMEOUT_US	1000
+#define PLL_BYPASS_WAIT_US	500
+
+struct atlantis_clk_common {
+	int clkid;
+	struct regmap *regmap;
+	struct clk_hw hw;
+};
+
+static inline struct atlantis_clk_common *
+hw_to_atlantis_clk_common(struct clk_hw *hw)
+{
+	return container_of(hw, struct atlantis_clk_common, hw);
+}
+
+struct atlantis_clk_mux_config {
+	u8 shift;
+	u8 width;
+	u32 reg_offset;
+};
+
+struct atlantis_clk_mux {
+	struct atlantis_clk_common common;
+	struct atlantis_clk_mux_config config;
+};
+
+struct atlantis_clk_gate_config {
+	u32 reg_offset;
+	u32 enable;
+};
+
+struct atlantis_clk_gate {
+	struct atlantis_clk_common common;
+	struct atlantis_clk_gate_config config;
+};
+
+struct atlantis_clk_divider_config {
+	u8 shift;
+	u8 width;
+	u32 flags;
+	u32 reg_offset;
+};
+
+struct atlantis_clk_divider {
+	struct atlantis_clk_common common;
+	struct atlantis_clk_divider_config config;
+};
+
+struct atlantis_clk_pll_config {
+	u32 tbl_num;
+	u32 reg_offset;
+	u32 en_reg_offset;
+	u32 cg_reg_offset;
+	u32 cg_reg_enable;
+};
+
+/* Models a PLL with Bypass Functionality and Enable Bit + an optional Gate Clock at it's output */
+struct atlantis_clk_pll {
+	struct atlantis_clk_common common;
+	struct atlantis_clk_pll_config config;
+};
+
+struct atlantis_clk_gate_shared_config {
+	u32 reg_offset;
+	u32 enable;
+	unsigned int *share_count;
+	spinlock_t *refcount_lock;
+};
+
+struct atlantis_clk_gate_shared {
+	struct atlantis_clk_common common;
+	struct atlantis_clk_gate_shared_config config;
+};
+
+struct atlantis_clk_fixed_factor_config {
+	unsigned int mult;
+	unsigned int div;
+};
+
+struct atlantis_clk_fixed_factor {
+	struct atlantis_clk_fixed_factor_config config;
+	struct atlantis_clk_common common;
+};
+
+static inline struct atlantis_clk_mux *hw_to_atlantis_clk_mux(struct clk_hw *hw)
+{
+	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
+
+	return container_of(common, struct atlantis_clk_mux, common);
+}
+
+static inline struct atlantis_clk_gate *
+hw_to_atlantis_clk_gate(struct clk_hw *hw)
+{
+	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
+
+	return container_of(common, struct atlantis_clk_gate, common);
+}
+
+static inline struct atlantis_clk_divider *
+hw_to_atlantis_clk_divider(struct clk_hw *hw)
+{
+	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
+
+	return container_of(common, struct atlantis_clk_divider, common);
+}
+
+static inline struct atlantis_clk_pll *hw_to_atlantis_pll(struct clk_hw *hw)
+{
+	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
+
+	return container_of(common, struct atlantis_clk_pll, common);
+}
+
+static inline struct atlantis_clk_gate_shared *
+hw_to_atlantis_clk_gate_shared(struct clk_hw *hw)
+{
+	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
+
+	return container_of(common, struct atlantis_clk_gate_shared, common);
+}
+
+static inline struct atlantis_clk_fixed_factor *
+hw_to_atlantis_clk_fixed_factor(struct clk_hw *hw)
+{
+	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
+
+	return container_of(common, struct atlantis_clk_fixed_factor, common);
+}
+
+static u8 atlantis_clk_mux_get_parent(struct clk_hw *hw)
+{
+	struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
+	u32 val;
+
+	regmap_read(mux->common.regmap, mux->config.reg_offset, &val);
+	val >>= mux->config.shift;
+	val &= (BIT(mux->config.width) - 1);
+
+	return val;
+}
+
+static int atlantis_clk_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
+	u32 val = index;
+
+	return regmap_update_bits(mux->common.regmap, mux->config.reg_offset,
+				  (BIT(mux->config.width) - 1)
+					  << mux->config.shift,
+				  val << mux->config.shift);
+}
+
+static int atlantis_clk_mux_determine_rate(struct clk_hw *hw,
+					   struct clk_rate_request *req)
+{
+	return clk_mux_determine_rate_flags(hw, req, hw->init->flags);
+}
+
+static const struct clk_ops atlantis_clk_mux_ops = {
+	.get_parent = atlantis_clk_mux_get_parent,
+	.set_parent = atlantis_clk_mux_set_parent,
+	.determine_rate = atlantis_clk_mux_determine_rate,
+};
+
+static void atlantis_clk_gate_endisable(struct clk_hw *hw, int enable)
+{
+	struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);
+	u32 val;
+
+	if (enable)
+		val = gate->config.enable;
+	else
+		val = ~(gate->config.enable);
+
+	regmap_update_bits(gate->common.regmap, gate->config.reg_offset,
+			   gate->config.enable, val);
+}
+
+static int atlantis_clk_gate_enable(struct clk_hw *hw)
+{
+	atlantis_clk_gate_endisable(hw, 1);
+
+	return 0;
+}
+
+static void atlantis_clk_gate_disable(struct clk_hw *hw)
+{
+	atlantis_clk_gate_endisable(hw, 0);
+}
+
+static int atlantis_clk_gate_is_enabled(struct clk_hw *hw)
+{
+	struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);
+	u32 val;
+
+	regmap_read(gate->common.regmap, gate->config.reg_offset, &val);
+
+	val &= gate->config.enable;
+
+	return !!val;
+}
+
+static const struct clk_ops atlantis_clk_gate_ops = {
+	.enable = atlantis_clk_gate_enable,
+	.disable = atlantis_clk_gate_disable,
+	.is_enabled = atlantis_clk_gate_is_enabled,
+};
+
+static unsigned long atlantis_clk_divider_recalc_rate(struct clk_hw *hw,
+						      unsigned long parent_rate)
+{
+	struct atlantis_clk_divider *divider = hw_to_atlantis_clk_divider(hw);
+	u32 val;
+
+	regmap_read(divider->common.regmap, divider->config.reg_offset, &val);
+
+	val >>= divider->config.shift;
+	val &= ((1 << (divider->config.width)) - 1);
+
+	return DIV_ROUND_UP_ULL((u64)parent_rate, val + 1);
+}
+
+static const struct clk_ops atlantis_clk_divider_ops = {
+	.recalc_rate = atlantis_clk_divider_recalc_rate,
+};
+
+static unsigned long
+atlantis_clk_fixed_factor_recalc_rate(struct clk_hw *hw,
+				      unsigned long parent_rate)
+{
+	struct atlantis_clk_fixed_factor *factor =
+		hw_to_atlantis_clk_fixed_factor(hw);
+	unsigned long long rate;
+
+	rate = (unsigned long long)parent_rate * factor->config.mult;
+	do_div(rate, factor->config.div);
+	return (unsigned long)rate;
+}
+
+static const struct clk_ops atlantis_clk_fixed_factor_ops = {
+	.recalc_rate = atlantis_clk_fixed_factor_recalc_rate,
+};
+
+static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
+{
+	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
+	u32 val, en_val, cg_val;
+
+	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
+	regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
+	regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);
+
+	/* Check if PLL is powered on, locked, not bypassed and Gate clk is enabled */
+	return !!(en_val & PLL_CFG_EN_BIT) && !!(val & PLL_CFG_LOCK_BIT) &&
+	       (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
+	       !(val & PLL_CFG_BYPASS_BIT);
+}
+
+static int atlantis_clk_pll_enable(struct clk_hw *hw)
+{
+	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
+	u32 val, en_val, cg_val;
+	int ret;
+
+	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
+	regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
+	regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);
+
+	/* Check if PLL is already enabled, locked, not bypassed and Gate clk is enabled */
+	if ((en_val & PLL_CFG_EN_BIT) && (val & PLL_CFG_LOCK_BIT) &&
+	    (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
+	    !(val & PLL_CFG_BYPASS_BIT)) {
+		return 0;
+	}
+
+	/* Step 1: Set bypass mode first */
+	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
+			   PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);
+
+	/* Step 2: Enable PLL (clear then set power bit) */
+	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
+			   PLL_CFG_EN_BIT, 0);
+
+	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
+			   PLL_CFG_EN_BIT, PLL_CFG_EN_BIT);
+
+	/* Step 3: Wait for PLL lock */
+	ret = regmap_read_poll_timeout(pll->common.regmap,
+				       pll->config.reg_offset, val,
+				       val & PLL_CFG_LOCK_BIT, 10,
+				       PLL_BYPASS_WAIT_US);
+	if (ret) {
+		pr_err("PLL failed to lock within timeout\n");
+		return ret;
+	}
+
+	/* Step 4: Switch from bypass to PLL output */
+	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
+			   PLL_CFG_BYPASS_BIT, 0);
+
+	/* Enable Gate clk at PLL Output */
+	return regmap_update_bits(pll->common.regmap, pll->config.cg_reg_offset,
+				  pll->config.cg_reg_enable,
+				  pll->config.cg_reg_enable);
+}
+
+static void atlantis_clk_pll_disable(struct clk_hw *hw)
+{
+	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
+
+	/* Step 1: Switch to bypass mode before disabling */
+	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
+			   PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);
+	/* Step 2: Power down PLL */
+	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
+			   PLL_CFG_EN_BIT, 0);
+}
+
+static unsigned long atlantis_clk_pll_recalc_rate(struct clk_hw *hw,
+						  unsigned long parent_rate)
+{
+	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
+
+	u32 val, refdiv, fbdiv, postdiv1, postdiv2;
+	u64 fout;
+
+	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
+
+	if (val & PLL_CFG_BYPASS_BIT)
+		return parent_rate;
+
+	refdiv = FIELD_GET(PLL_CFG_REFDIV_MASK, val);
+	fbdiv = FIELD_GET(PLL_CFG_FBDIV_MASK, val);
+	postdiv1 = FIELD_GET(PLL_CFG_POSTDIV1_MASK, val);
+	postdiv2 = FIELD_GET(PLL_CFG_POSTDIV2_MASK, val);
+
+	if (!refdiv)
+		refdiv = 1;
+	if (!postdiv1)
+		postdiv1 = 1;
+	if (!postdiv2)
+		postdiv2 = 1;
+	if (!fbdiv)
+		return 0;
+
+	fout = div64_u64((u64)parent_rate * fbdiv,
+			 refdiv * postdiv1 * postdiv2);
+
+	return fout;
+}
+
+static const struct clk_ops atlantis_clk_pll_ops = {
+	.enable = atlantis_clk_pll_enable,
+	.disable = atlantis_clk_pll_disable,
+	.recalc_rate = atlantis_clk_pll_recalc_rate,
+	.is_enabled = atlantis_clk_pll_is_enabled,
+};
+
+static int atlantis_clk_gate_shared_enable(struct clk_hw *hw)
+{
+	struct atlantis_clk_gate_shared *gate =
+		hw_to_atlantis_clk_gate_shared(hw);
+	bool need_enable;
+	u32 reg;
+
+	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
+	{
+		need_enable = (*gate->config.share_count)++ == 0;
+		if (need_enable) {
+			regmap_read(gate->common.regmap,
+				    gate->config.reg_offset, &reg);
+			reg |= gate->config.enable;
+			regmap_write(gate->common.regmap,
+				     gate->config.reg_offset, reg);
+		}
+	}
+
+	if (need_enable) {
+		regmap_read(gate->common.regmap, gate->config.reg_offset, &reg);
+
+		if (!(reg & gate->config.enable)) {
+			pr_warn("%s: gate enable %d failed to enable\n",
+				clk_hw_get_name(hw), gate->config.enable);
+			return -EIO;
+		}
+	}
+
+	return 0;
+}
+
+static void atlantis_clk_gate_shared_disable(struct clk_hw *hw)
+{
+	struct atlantis_clk_gate_shared *gate =
+		hw_to_atlantis_clk_gate_shared(hw);
+	u32 reg;
+
+	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
+	{
+		if (WARN_ON(*gate->config.share_count == 0))
+			return;
+		if (--(*gate->config.share_count) > 0)
+			return;
+
+		regmap_read(gate->common.regmap, gate->config.reg_offset, &reg);
+		reg &= ~gate->config.enable;
+		regmap_write(gate->common.regmap, gate->config.reg_offset, reg);
+	}
+}
+
+static int atlantis_clk_gate_shared_is_enabled(struct clk_hw *hw)
+{
+	struct atlantis_clk_gate_shared *gate =
+		hw_to_atlantis_clk_gate_shared(hw);
+	u32 reg;
+
+	regmap_read(gate->common.regmap, gate->config.reg_offset, &reg);
+
+	return !!(reg & gate->config.enable);
+}
+
+static void atlantis_clk_gate_shared_disable_unused(struct clk_hw *hw)
+{
+	struct atlantis_clk_gate_shared *gate =
+		hw_to_atlantis_clk_gate_shared(hw);
+
+	u32 reg;
+
+	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
+	{
+		if (*gate->config.share_count == 0) {
+			regmap_read(gate->common.regmap,
+				    gate->config.reg_offset, &reg);
+			reg &= ~gate->config.enable;
+			regmap_write(gate->common.regmap,
+				     gate->config.reg_offset, reg);
+		}
+	}
+}
+
+static const struct clk_ops atlantis_clk_gate_shared_ops = {
+	.enable = atlantis_clk_gate_shared_enable,
+	.disable = atlantis_clk_gate_shared_disable,
+	.disable_unused = atlantis_clk_gate_shared_disable_unused,
+	.is_enabled = atlantis_clk_gate_shared_is_enabled,
+};
+
+#define ATLANTIS_PLL_CONFIG(_reg_offset, _en_reg_offset, _cg_reg_offset, \
+			    _cg_reg_enable)                              \
+	{                                                                \
+		.reg_offset = (_reg_offset),                             \
+		.en_reg_offset = (_en_reg_offset),                       \
+		.cg_reg_offset = (_cg_reg_offset),                       \
+		.cg_reg_enable = (_cg_reg_enable),                       \
+	}
+
+#define ATLANTIS_PLL_DEFINE(_clkid, _name, _parent, _reg_offset,               \
+			    _en_reg_offset, _cg_reg_offset, _cg_reg_enable,    \
+			    _flags)                                            \
+	static struct atlantis_clk_pll _name = {                               \
+		.config = ATLANTIS_PLL_CONFIG(_reg_offset, _en_reg_offset,     \
+					      _cg_reg_offset, _cg_reg_enable), \
+		.common = { .clkid = _clkid,                                   \
+			    .hw.init = CLK_HW_INIT_PARENTS_DATA(               \
+				    #_name, _parent, &atlantis_clk_pll_ops,    \
+				    _flags) },                                 \
+	}
+#define ATLANTIS_MUX_CONFIG(_shift, _width, _reg_offset)                    \
+	{                                                                   \
+		.shift = _shift, .width = _width, .reg_offset = _reg_offset \
+	}
+
+#define ATLANTIS_MUX_DEFINE(_clkid, _name, _parents, _reg_offset, _shift,    \
+			    _width, _flags)                                  \
+	static struct atlantis_clk_mux _name = {                             \
+		.config = ATLANTIS_MUX_CONFIG(_shift, _width, _reg_offset),  \
+		.common = { .clkid = _clkid,                                 \
+			    .hw.init = CLK_HW_INIT_PARENTS_DATA(             \
+				    #_name, _parents, &atlantis_clk_mux_ops, \
+				    _flags) }                                \
+	}
+
+#define ATLANTIS_DIVIDER_CONFIG(_shift, _width, _flags, _reg_offset) \
+	{                                                            \
+		.shift = _shift, .width = _width, .flags = _flags,   \
+		.reg_offset = _reg_offset                            \
+	}
+
+#define ATLANTIS_DIVIDER_DEFINE(_clkid, _name, _parent, _reg_offset, _shift, \
+				_width, _divflags, _flags)                   \
+	static struct atlantis_clk_divider _name = {                         \
+		.config = ATLANTIS_DIVIDER_CONFIG(_shift, _width, _divflags, \
+						  _reg_offset),              \
+		.common = { .clkid = _clkid,                                 \
+			    .hw.init = CLK_HW_INIT_HW(                       \
+				    #_name, &_parent.common.hw,              \
+				    &atlantis_clk_divider_ops, _flags) }     \
+	}
+#define ATLANTIS_GATE_CONFIG(_enable, _reg_offset)           \
+	{                                                    \
+		.enable = _enable, .reg_offset = _reg_offset \
+	}
+
+#define ATLANTIS_GATE_DEFINE(_clkid, _name, _parent, _reg_offset, _enable, \
+			     _flags)                                       \
+	static struct atlantis_clk_gate _name = {                          \
+		.config = ATLANTIS_GATE_CONFIG(_enable, _reg_offset),      \
+		.common = { .clkid = _clkid,                               \
+			    .hw.init = CLK_HW_INIT_HW(                     \
+				    #_name, &_parent.common.hw,            \
+				    &atlantis_clk_gate_ops, _flags) }      \
+	}
+#define ATLANTIS_GATE_SHARED_CONFIG(_reg_offset, _enable, _share_count)      \
+	{                                                                    \
+		.reg_offset = _reg_offset, .enable = _enable,                \
+		.share_count = _share_count, .refcount_lock = &refcount_lock \
+	}
+#define ATLANTIS_GATE_SHARED_DEFINE(_clkid, _name, _parent, _reg_offset,     \
+				    _enable, _share_count, _flags)           \
+	static struct atlantis_clk_gate_shared _name = {                     \
+		.config = ATLANTIS_GATE_SHARED_CONFIG(_reg_offset, _enable,  \
+						      _share_count),         \
+		.common = { .clkid = _clkid,                                 \
+			    .hw.init = CLK_HW_INIT_HW(                       \
+				    #_name, &_parent.common.hw,              \
+				    &atlantis_clk_gate_shared_ops, _flags) } \
+	}
+#define ATLANTIS_CLK_FIXED_FACTOR_DEFINE(_clkid, _name, _parent, _mult, _div, \
+					 _flags)                              \
+	static struct atlantis_clk_fixed_factor _name = {                     \
+		.config = { .mult = _mult, .div = _div },                     \
+		.common = { .clkid = _clkid,                                  \
+			    .hw.init = CLK_HW_INIT_HW(                        \
+				    #_name, &_parent.common.hw,               \
+				    &atlantis_clk_fixed_factor_ops, _flags) } \
+	}
+
+static DEFINE_SPINLOCK(refcount_lock); /* Lock for refcount value accesses */
+
+static const struct regmap_config atlantis_prcm_regmap_config = {
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.max_register = 0xFFFC,
+	.cache_type = REGCACHE_NONE,
+};
+
+struct atlantis_prcm_data {
+	struct clk_hw **hws;
+	size_t num;
+	const char *reset_name;
+};
+
+static const struct clk_parent_data osc_24m_clk[] = {
+	{ .index = 0 },
+};
+
+ATLANTIS_PLL_DEFINE(CLK_RCPU_PLL, rcpu_pll_clk, osc_24m_clk, RCPU_PLL_CFG_REG,
+		    PLL_RCPU_EN_REG, BUS_CG_REG, 0, /* No Gate Clk at Output */
+		    CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL);
+
+static const struct clk_parent_data rcpu_root_parents[] = {
+	{ .index = 0 },
+	{ .hw = &rcpu_pll_clk.common.hw },
+};
+
+ATLANTIS_MUX_DEFINE(CLK_RCPU_ROOT, rcpu_root_mux, rcpu_root_parents,
+		    RCPU_DIV_CFG_REG, 0, 1, CLK_SET_RATE_NO_REPARENT);
+
+ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_DIV2, rcpu_div2_clk, rcpu_root_mux,
+			RCPU_DIV_CFG_REG, 2, 4, 0, 0);
+ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_DIV4, rcpu_div4_clk, rcpu_root_mux,
+			RCPU_DIV_CFG_REG, 7, 4, 0, 0);
+ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_RTC, rcpu_rtc_clk, rcpu_div4_clk,
+			RCPU_DIV_CFG_REG, 12, 6, 0, 0);
+
+ATLANTIS_GATE_DEFINE(CLK_SMNDMA0_ACLK, smndma0_aclk, rcpu_div2_clk,
+		     RCPU_BLK_CG_REG, BIT(0), 0);
+ATLANTIS_GATE_DEFINE(CLK_SMNDMA1_ACLK, smndma1_aclk, rcpu_div2_clk,
+		     RCPU_BLK_CG_REG, BIT(1), 0);
+ATLANTIS_GATE_DEFINE(CLK_WDT0_PCLK, wdt0_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
+		     BIT(2), 0);
+ATLANTIS_GATE_DEFINE(CLK_WDT1_PCLK, wdt1_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
+		     BIT(3), 0);
+ATLANTIS_GATE_DEFINE(CLK_TIMER_PCLK, timer_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
+		     BIT(4), 0);
+ATLANTIS_GATE_DEFINE(CLK_PVTC_PCLK, pvtc_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
+		     BIT(12), 0);
+ATLANTIS_GATE_DEFINE(CLK_PMU_PCLK, pmu_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
+		     BIT(13), 0);
+ATLANTIS_GATE_DEFINE(CLK_MAILBOX_HCLK, mb_hclk, rcpu_div2_clk, RCPU_BLK_CG_REG,
+		     BIT(14), 0);
+ATLANTIS_GATE_DEFINE(CLK_SEC_SPACC_HCLK, sec_spacc_hclk, rcpu_div2_clk,
+		     RCPU_BLK_CG_REG, BIT(26), 0);
+ATLANTIS_GATE_DEFINE(CLK_SEC_OTP_HCLK, sec_otp_hclk, rcpu_div2_clk,
+		     RCPU_BLK_CG_REG, BIT(28), 0);
+ATLANTIS_GATE_DEFINE(CLK_TRNG_PCLK, trng_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
+		     BIT(29), 0);
+ATLANTIS_GATE_DEFINE(CLK_SEC_CRC_HCLK, sec_crc_hclk, rcpu_div2_clk,
+		     RCPU_BLK_CG_REG, BIT(30), 0);
+
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SMN_HCLK, rcpu_smn_hclk, rcpu_div2_clk, 1,
+				 1, 0);
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_AHB0_HCLK, rcpu_ahb0_hclk, rcpu_div2_clk,
+				 1, 1, 0);
+
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SMN_PCLK, rcpu_smn_pclk, rcpu_div4_clk, 1,
+				 1, 0);
+
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SMN_CLK, rcpu_smn_clk, rcpu_root_mux, 1, 1,
+				 0);
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SCRATCHPAD_CLK, rcpu_scratchpad_aclk,
+				 rcpu_root_mux, 1, 1, 0);
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_RCPU_CORE_CLK, rcpu_core_clk,
+				 rcpu_root_mux, 1, 1, 0);
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_RCPU_ROM_CLK, rcpu_rom_aclk, rcpu_root_mux,
+				 1, 1, 0);
+
+static struct atlantis_clk_fixed_factor
+	otp_load_clk = { .config = { .mult = 1, .div = 1 },
+			 .common = {
+				 .clkid = CLK_OTP_LOAD_CLK,
+				 .hw.init = CLK_HW_INIT_PARENTS_DATA(
+					 "otp_load_clk", osc_24m_clk,
+					 &atlantis_clk_fixed_factor_ops,
+					 CLK_SET_RATE_NO_REPARENT),
+			 } };
+
+ATLANTIS_PLL_DEFINE(CLK_NOC_PLL, nocc_pll_clk, osc_24m_clk,
+		    RCPU_NOCC_PLL_CFG_REG, PLL_NOCC_EN_REG, BUS_CG_REG, BIT(0),
+		    CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL);
+
+static const struct clk_parent_data nocc_mux_parents[] = {
+	{ .index = 0 },
+	{ .hw = &nocc_pll_clk.common.hw },
+};
+
+ATLANTIS_MUX_DEFINE(CLK_NOCC_CLK, nocc_clk_mux, nocc_mux_parents,
+		    RCPU_NOCC_CLK_CFG_REG, 0, 1, CLK_SET_RATE_NO_REPARENT);
+
+ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_DIV2, nocc_div2_clk, nocc_clk_mux,
+			RCPU_NOCC_CLK_CFG_REG, 1, 4, 0, 0);
+ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_DIV4, nocc_div4_clk, nocc_clk_mux,
+			RCPU_NOCC_CLK_CFG_REG, 5, 4, 0, 0);
+ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_RTC, nocc_rtc_clk, nocc_div4_clk,
+			RCPU_NOCC_CLK_CFG_REG, 9, 6, 0, 0);
+ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_CAN, nocc_can_div, nocc_clk_mux,
+			RCPU_NOCC_CLK_CFG_REG, 15, 4, 0, 0);
+
+static unsigned int refcnt_qspi;
+ATLANTIS_GATE_SHARED_DEFINE(CLK_QSPI_SCLK, qspi_sclk, nocc_clk_mux,
+			    LSIO_BLK_CG_REG, BIT(0), &refcnt_qspi, 0);
+ATLANTIS_GATE_SHARED_DEFINE(CLK_QSPI_HCLK, qspi_hclk, nocc_div2_clk,
+			    LSIO_BLK_CG_REG, BIT(0), &refcnt_qspi, 0);
+ATLANTIS_GATE_DEFINE(CLK_I2C0_PCLK, i2c0_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(1), 0);
+ATLANTIS_GATE_DEFINE(CLK_I2C1_PCLK, i2c1_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(2), 0);
+ATLANTIS_GATE_DEFINE(CLK_I2C2_PCLK, i2c2_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(3), 0);
+ATLANTIS_GATE_DEFINE(CLK_I2C3_PCLK, i2c3_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(4), 0);
+ATLANTIS_GATE_DEFINE(CLK_I2C4_PCLK, i2c4_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(5), 0);
+
+ATLANTIS_GATE_DEFINE(CLK_UART0_PCLK, uart0_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(6), 0);
+ATLANTIS_GATE_DEFINE(CLK_UART1_PCLK, uart1_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(7), 0);
+ATLANTIS_GATE_DEFINE(CLK_UART2_PCLK, uart2_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(8), 0);
+ATLANTIS_GATE_DEFINE(CLK_UART3_PCLK, uart3_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(9), 0);
+ATLANTIS_GATE_DEFINE(CLK_UART4_PCLK, uart4_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(10), 0);
+ATLANTIS_GATE_DEFINE(CLK_SPI0_PCLK, spi0_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(11), 0);
+ATLANTIS_GATE_DEFINE(CLK_SPI1_PCLK, spi1_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(12), 0);
+ATLANTIS_GATE_DEFINE(CLK_SPI2_PCLK, spi2_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(13), 0);
+ATLANTIS_GATE_DEFINE(CLK_SPI3_PCLK, spi3_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(14), 0);
+ATLANTIS_GATE_DEFINE(CLK_GPIO_PCLK, gpio_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
+		     BIT(15), 0);
+
+static unsigned int refcnt_can0;
+ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN0_HCLK, can0_hclk, nocc_div2_clk,
+			    LSIO_BLK_CG_REG, BIT(17), &refcnt_can0, 0);
+ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN0_CLK, can0_clk, nocc_can_div,
+			    LSIO_BLK_CG_REG, BIT(17), &refcnt_can0, 0);
+
+static unsigned int refcnt_can1;
+ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN1_HCLK, can1_hclk, nocc_div2_clk,
+			    LSIO_BLK_CG_REG, BIT(18), &refcnt_can1, 0);
+ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN1_CLK, can1_clk, nocc_can_div,
+			    LSIO_BLK_CG_REG, BIT(18), &refcnt_can1, 0);
+
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_CAN0_TIMER_CLK, can0_timer_clk,
+				 nocc_rtc_clk, 1, 1, 0);
+ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_CAN1_TIMER_CLK, can1_timer_clk,
+				 nocc_rtc_clk, 1, 1, 0);
+
+static struct clk_hw *atlantis_rcpu_clks[] = {
+	[CLK_RCPU_PLL]		= &rcpu_pll_clk.common.hw,
+	[CLK_RCPU_ROOT]		= &rcpu_root_mux.common.hw,
+	[CLK_RCPU_DIV2]		= &rcpu_div2_clk.common.hw,
+	[CLK_RCPU_DIV4]		= &rcpu_div4_clk.common.hw,
+	[CLK_RCPU_RTC]		= &rcpu_rtc_clk.common.hw,
+	[CLK_SMNDMA0_ACLK]	= &smndma0_aclk.common.hw,
+	[CLK_SMNDMA1_ACLK]	= &smndma1_aclk.common.hw,
+	[CLK_WDT0_PCLK]		= &wdt0_pclk.common.hw,
+	[CLK_WDT1_PCLK]		= &wdt1_pclk.common.hw,
+	[CLK_TIMER_PCLK]	= &timer_pclk.common.hw,
+	[CLK_PVTC_PCLK]		= &pvtc_pclk.common.hw,
+	[CLK_PMU_PCLK]		= &pmu_pclk.common.hw,
+	[CLK_MAILBOX_HCLK]	= &mb_hclk.common.hw,
+	[CLK_SEC_SPACC_HCLK]	= &sec_spacc_hclk.common.hw,
+	[CLK_SEC_OTP_HCLK]	= &sec_otp_hclk.common.hw,
+	[CLK_TRNG_PCLK]		= &trng_pclk.common.hw,
+	[CLK_SEC_CRC_HCLK]	= &sec_crc_hclk.common.hw,
+	[CLK_SMN_HCLK]		= &rcpu_smn_hclk.common.hw,
+	[CLK_AHB0_HCLK]		= &rcpu_ahb0_hclk.common.hw,
+	[CLK_SMN_PCLK]		= &rcpu_smn_pclk.common.hw,
+	[CLK_SMN_CLK]		= &rcpu_smn_clk.common.hw,
+	[CLK_SCRATCHPAD_CLK]	= &rcpu_scratchpad_aclk.common.hw,
+	[CLK_RCPU_CORE_CLK]	= &rcpu_core_clk.common.hw,
+	[CLK_RCPU_ROM_CLK]	= &rcpu_rom_aclk.common.hw,
+	[CLK_OTP_LOAD_CLK]	= &otp_load_clk.common.hw,
+	[CLK_NOC_PLL]		= &nocc_pll_clk.common.hw,
+	[CLK_NOCC_CLK]		= &nocc_clk_mux.common.hw,
+	[CLK_NOCC_DIV2]		= &nocc_div2_clk.common.hw,
+	[CLK_NOCC_DIV4]		= &nocc_div4_clk.common.hw,
+	[CLK_NOCC_RTC]		= &nocc_rtc_clk.common.hw,
+	[CLK_NOCC_CAN]		= &nocc_can_div.common.hw,
+	[CLK_QSPI_SCLK]		= &qspi_sclk.common.hw,
+	[CLK_QSPI_HCLK]		= &qspi_hclk.common.hw,
+	[CLK_I2C0_PCLK]		= &i2c0_pclk.common.hw,
+	[CLK_I2C1_PCLK]		= &i2c1_pclk.common.hw,
+	[CLK_I2C2_PCLK]		= &i2c2_pclk.common.hw,
+	[CLK_I2C3_PCLK]		= &i2c3_pclk.common.hw,
+	[CLK_I2C4_PCLK]		= &i2c4_pclk.common.hw,
+	[CLK_UART0_PCLK]	= &uart0_pclk.common.hw,
+	[CLK_UART1_PCLK]	= &uart1_pclk.common.hw,
+	[CLK_UART2_PCLK]	= &uart2_pclk.common.hw,
+	[CLK_UART3_PCLK]	= &uart3_pclk.common.hw,
+	[CLK_UART4_PCLK]	= &uart4_pclk.common.hw,
+	[CLK_SPI0_PCLK]		= &spi0_pclk.common.hw,
+	[CLK_SPI1_PCLK]		= &spi1_pclk.common.hw,
+	[CLK_SPI2_PCLK]		= &spi2_pclk.common.hw,
+	[CLK_SPI3_PCLK]		= &spi3_pclk.common.hw,
+	[CLK_GPIO_PCLK]		= &gpio_pclk.common.hw,
+	[CLK_CAN0_HCLK]		= &can0_hclk.common.hw,
+	[CLK_CAN0_CLK]		= &can0_clk.common.hw,
+	[CLK_CAN1_HCLK]		= &can1_hclk.common.hw,
+	[CLK_CAN1_CLK]		= &can1_clk.common.hw,
+	[CLK_CAN0_TIMER_CLK]	= &can0_timer_clk.common.hw,
+	[CLK_CAN1_TIMER_CLK]	= &can1_timer_clk.common.hw,
+};
+
+static const struct atlantis_prcm_data atlantis_prcm_rcpu_data = {
+	.hws = atlantis_rcpu_clks,
+	.num = ARRAY_SIZE(atlantis_rcpu_clks),
+	.reset_name = "rcpu-reset"
+};
+
+static int atlantis_prcm_clocks_register(struct device *dev,
+					 struct regmap *regmap,
+					 const struct atlantis_prcm_data *data)
+{
+	struct clk_hw_onecell_data *clk_data;
+	int i, ret;
+	size_t num_clks = data->num;
+
+	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num),
+				GFP_KERNEL);
+	if (!clk_data)
+		return -ENOMEM;
+
+	for (i = 0; i < data->num; i++) {
+		struct clk_hw *hw = data->hws[i];
+		struct atlantis_clk_common *common =
+			hw_to_atlantis_clk_common(hw);
+		common->regmap = regmap;
+
+		ret = devm_clk_hw_register(dev, hw);
+
+		if (ret)
+			return ret;
+
+		clk_data->hws[common->clkid] = hw;
+	}
+
+	clk_data->num = num_clks;
+
+	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
+
+	return ret;
+}
+
+static int atlantis_prcm_probe(struct platform_device *pdev)
+{
+	const struct atlantis_prcm_data *data;
+	struct auxiliary_device *reset_adev;
+	struct regmap *regmap;
+	void __iomem *base;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return dev_err_probe(dev, PTR_ERR(base),
+				     "Failed to map registers\n");
+
+	regmap = devm_regmap_init_mmio(dev, base, &atlantis_prcm_regmap_config);
+	if (IS_ERR(regmap))
+		return dev_err_probe(dev, PTR_ERR(regmap),
+				     "Failed to init regmap\n");
+
+	data = of_device_get_match_data(dev);
+
+	ret = atlantis_prcm_clocks_register(dev, regmap, data);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register clocks\n");
+
+	reset_adev = devm_auxiliary_device_create(dev, data->reset_name, NULL);
+	if (!reset_adev)
+		return dev_err_probe(dev, -ENODEV, "failed to register resets\n");
+
+	return 0;
+}
+
+static const struct of_device_id atlantis_prcm_of_match[] = {
+	{
+		.compatible = "tenstorrent,atlantis-prcm-rcpu",
+		.data = &atlantis_prcm_rcpu_data,
+	},
+	{}
+
+};
+MODULE_DEVICE_TABLE(of, atlantis_prcm_of_match);
+
+static struct platform_driver atlantis_prcm_driver = {
+	.probe = atlantis_prcm_probe,
+	.driver = {
+		.name = "atlantis-prcm",
+		.of_match_table = atlantis_prcm_of_match,
+	},
+};
+module_platform_driver(atlantis_prcm_driver);
+
+MODULE_DESCRIPTION("Tenstorrent Atlantis PRCM Clock Controller Driver");
+MODULE_AUTHOR("Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>");
+MODULE_LICENSE("GPL");

-- 
2.43.0


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

* Re: [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver
  2026-03-03 17:36 ` [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver Anirudh Srinivasan
@ 2026-03-03 22:32   ` Brian Masney
  2026-03-04 16:40     ` Anirudh Srinivasan
  0 siblings, 1 reply; 8+ messages in thread
From: Brian Masney @ 2026-03-03 22:32 UTC (permalink / raw)
  To: Anirudh Srinivasan
  Cc: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Philipp Zabel,
	linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, Krzysztof Kozlowski

Hi Anirudh,

Thanks for the patch. A few minor comments below with some minor
nitpicks, additional places to use FIELD_GET(), plus some suggestions
for additional regmap helpers to use.

On Tue, Mar 03, 2026 at 11:36:09AM -0600, Anirudh Srinivasan wrote:
> Add driver for clock controller in Tenstorrent Atlantis SoC. This version
> of the driver covers clocks from RCPU subsystem.
> 
> 5 types of clocks generated by this controller: PLLs (PLLs
> with bypass functionality and an additional Gate clk at output), Shared
> Gates (Multiple Gate clks that share an enable bit), standard Muxes,
> Dividers and Gates. All clocks are implemented using custom clk ops and
> use the regmap interface associated with the syscon. All clocks are derived
> from a 24 Mhz oscillator.
> 
> The reset controller is also setup as an auxiliary device of the clock
> controller.
> 
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
> ---
>  MAINTAINERS                             |   1 +
>  drivers/clk/Kconfig                     |   1 +
>  drivers/clk/Makefile                    |   1 +
>  drivers/clk/tenstorrent/Kconfig         |  14 +
>  drivers/clk/tenstorrent/Makefile        |   3 +
>  drivers/clk/tenstorrent/atlantis-prcm.c | 893 ++++++++++++++++++++++++++++++++
>  6 files changed, 913 insertions(+)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 493d007d3c65..a7783eb0a7de 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22812,6 +22812,7 @@ T:	git https://github.com/tenstorrent/linux.git
>  F:	Documentation/devicetree/bindings/clock/tenstorrent,atlantis-prcm-rcpu.yaml
>  F:	Documentation/devicetree/bindings/riscv/tenstorrent.yaml
>  F:	arch/riscv/boot/dts/tenstorrent/
> +F:	drivers/clk/tenstorrent/
>  F:	drivers/reset/reset-tenstorrent-atlantis.c
>  F:	include/dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h
>  
> diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> index 3d803b4cf5c1..8cc300b90b5f 100644
> --- a/drivers/clk/Kconfig
> +++ b/drivers/clk/Kconfig
> @@ -531,6 +531,7 @@ source "drivers/clk/starfive/Kconfig"
>  source "drivers/clk/sunxi/Kconfig"
>  source "drivers/clk/sunxi-ng/Kconfig"
>  source "drivers/clk/tegra/Kconfig"
> +source "drivers/clk/tenstorrent/Kconfig"
>  source "drivers/clk/thead/Kconfig"
>  source "drivers/clk/stm32/Kconfig"
>  source "drivers/clk/ti/Kconfig"
> diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
> index f7bce3951a30..f52cf3ac64fc 100644
> --- a/drivers/clk/Makefile
> +++ b/drivers/clk/Makefile
> @@ -155,6 +155,7 @@ obj-y					+= starfive/
>  obj-$(CONFIG_ARCH_SUNXI)		+= sunxi/
>  obj-y					+= sunxi-ng/
>  obj-$(CONFIG_ARCH_TEGRA)		+= tegra/
> +obj-y					+= tenstorrent/
>  obj-$(CONFIG_ARCH_THEAD)		+= thead/
>  obj-y					+= ti/
>  obj-$(CONFIG_CLK_UNIPHIER)		+= uniphier/
> diff --git a/drivers/clk/tenstorrent/Kconfig b/drivers/clk/tenstorrent/Kconfig
> new file mode 100644
> index 000000000000..9d4391eeeae0
> --- /dev/null
> +++ b/drivers/clk/tenstorrent/Kconfig
> @@ -0,0 +1,14 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config TENSTORRENT_ATLANTIS_PRCM
> +	tristate "Support for Tenstorrent Atlantis PRCM Clock Controller"
> +	depends on ARCH_TENSTORRENT || COMPILE_TEST
> +	default ARCH_TENSTORRENT
> +	select REGMAP_MMIO
> +	select AUXILIARY_BUS
> +	select MFD_SYSCON
> +	help
> +	  Say yes here to support the different clock
> +	  controllers found in the Tenstorrent Atlantis SoC.
> +	  This includes the clocks from the RCPU, HSIO, MMIO
> +	  and PCIE domain.
> diff --git a/drivers/clk/tenstorrent/Makefile b/drivers/clk/tenstorrent/Makefile
> new file mode 100644
> index 000000000000..95d87bac7bf5
> --- /dev/null
> +++ b/drivers/clk/tenstorrent/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +obj-$(CONFIG_TENSTORRENT_ATLANTIS_PRCM)	+= atlantis-prcm.o
> diff --git a/drivers/clk/tenstorrent/atlantis-prcm.c b/drivers/clk/tenstorrent/atlantis-prcm.c
> new file mode 100644
> index 000000000000..b0a8e3526901
> --- /dev/null
> +++ b/drivers/clk/tenstorrent/atlantis-prcm.c
> @@ -0,0 +1,893 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Tenstorrent Atlantis PRCM Clock Driver
> + *
> + * Copyright (c) 2026 Tenstorrent
> + */
> +
> +#include <dt-bindings/clock/tenstorrent,atlantis-prcm-rcpu.h>
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/clk-provider.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +
> +/* RCPU Clock Register Offsets */
> +#define RCPU_PLL_CFG_REG	0x0000
> +#define RCPU_NOCC_PLL_CFG_REG	0x0004
> +#define RCPU_NOCC_CLK_CFG_REG	0x0008
> +#define RCPU_DIV_CFG_REG	0x000C
> +#define RCPU_BLK_CG_REG		0x0014
> +#define LSIO_BLK_CG_REG		0x0018
> +#define PLL_RCPU_EN_REG		0x011c
> +#define PLL_NOCC_EN_REG		0x0120
> +#define BUS_CG_REG		0x01FC
> +
> +/* PLL Bit Definitions */
> +#define PLL_CFG_EN_BIT		BIT(0)
> +#define PLL_CFG_BYPASS_BIT	BIT(1)
> +#define PLL_CFG_REFDIV_MASK	GENMASK(7, 2)
> +#define PLL_CFG_REFDIV_SHIFT	2
> +#define PLL_CFG_POSTDIV1_MASK	GENMASK(10, 8)
> +#define PLL_CFG_POSTDIV1_SHIFT	8
> +#define PLL_CFG_POSTDIV2_MASK	GENMASK(13, 11)
> +#define PLL_CFG_POSTDIV2_SHIFT	11
> +#define PLL_CFG_FBDIV_MASK	GENMASK(25, 14)
> +#define PLL_CFG_FBDIV_SHIFT	14
> +#define PLL_CFG_LKDT_BIT	BIT(30)
> +#define PLL_CFG_LOCK_BIT	BIT(31)
> +#define PLL_LOCK_TIMEOUT_US	1000
> +#define PLL_BYPASS_WAIT_US	500
> +
> +struct atlantis_clk_common {
> +	int clkid;
> +	struct regmap *regmap;
> +	struct clk_hw hw;
> +};
> +
> +static inline struct atlantis_clk_common *
> +hw_to_atlantis_clk_common(struct clk_hw *hw)
> +{
> +	return container_of(hw, struct atlantis_clk_common, hw);
> +}
> +
> +struct atlantis_clk_mux_config {
> +	u8 shift;
> +	u8 width;
> +	u32 reg_offset;
> +};
> +
> +struct atlantis_clk_mux {
> +	struct atlantis_clk_common common;
> +	struct atlantis_clk_mux_config config;
> +};
> +
> +struct atlantis_clk_gate_config {
> +	u32 reg_offset;
> +	u32 enable;
> +};
> +
> +struct atlantis_clk_gate {
> +	struct atlantis_clk_common common;
> +	struct atlantis_clk_gate_config config;
> +};
> +
> +struct atlantis_clk_divider_config {
> +	u8 shift;
> +	u8 width;
> +	u32 flags;
> +	u32 reg_offset;
> +};
> +
> +struct atlantis_clk_divider {
> +	struct atlantis_clk_common common;
> +	struct atlantis_clk_divider_config config;
> +};
> +
> +struct atlantis_clk_pll_config {
> +	u32 tbl_num;
> +	u32 reg_offset;
> +	u32 en_reg_offset;
> +	u32 cg_reg_offset;
> +	u32 cg_reg_enable;
> +};
> +
> +/* Models a PLL with Bypass Functionality and Enable Bit + an optional Gate Clock at it's output */
> +struct atlantis_clk_pll {
> +	struct atlantis_clk_common common;
> +	struct atlantis_clk_pll_config config;
> +};
> +
> +struct atlantis_clk_gate_shared_config {
> +	u32 reg_offset;
> +	u32 enable;
> +	unsigned int *share_count;
> +	spinlock_t *refcount_lock;
> +};
> +
> +struct atlantis_clk_gate_shared {
> +	struct atlantis_clk_common common;
> +	struct atlantis_clk_gate_shared_config config;
> +};
> +
> +struct atlantis_clk_fixed_factor_config {
> +	unsigned int mult;
> +	unsigned int div;
> +};
> +
> +struct atlantis_clk_fixed_factor {
> +	struct atlantis_clk_fixed_factor_config config;
> +	struct atlantis_clk_common common;
> +};
> +
> +static inline struct atlantis_clk_mux *hw_to_atlantis_clk_mux(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
> +
> +	return container_of(common, struct atlantis_clk_mux, common);
> +}
> +
> +static inline struct atlantis_clk_gate *
> +hw_to_atlantis_clk_gate(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
> +
> +	return container_of(common, struct atlantis_clk_gate, common);
> +}
> +
> +static inline struct atlantis_clk_divider *
> +hw_to_atlantis_clk_divider(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
> +
> +	return container_of(common, struct atlantis_clk_divider, common);
> +}
> +
> +static inline struct atlantis_clk_pll *hw_to_atlantis_pll(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
> +
> +	return container_of(common, struct atlantis_clk_pll, common);
> +}
> +
> +static inline struct atlantis_clk_gate_shared *
> +hw_to_atlantis_clk_gate_shared(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
> +
> +	return container_of(common, struct atlantis_clk_gate_shared, common);
> +}
> +
> +static inline struct atlantis_clk_fixed_factor *
> +hw_to_atlantis_clk_fixed_factor(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_common *common = hw_to_atlantis_clk_common(hw);
> +
> +	return container_of(common, struct atlantis_clk_fixed_factor, common);
> +}
> +
> +static u8 atlantis_clk_mux_get_parent(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
> +	u32 val;
> +
> +	regmap_read(mux->common.regmap, mux->config.reg_offset, &val);
> +	val >>= mux->config.shift;
> +	val &= (BIT(mux->config.width) - 1);
> +
> +	return val;

FIELD_GET() ?

> +}
> +
> +static int atlantis_clk_mux_set_parent(struct clk_hw *hw, u8 index)
> +{
> +	struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
> +	u32 val = index;
> +
> +	return regmap_update_bits(mux->common.regmap, mux->config.reg_offset,
> +				  (BIT(mux->config.width) - 1)
> +					  << mux->config.shift,

This doesn't make the line that much longer, and is nicer to read:

    (BIT(mux->config.width) - 1) << mux->config.shift,

> +				  val << mux->config.shift);
> +}
> +
> +static int atlantis_clk_mux_determine_rate(struct clk_hw *hw,
> +					   struct clk_rate_request *req)
> +{
> +	return clk_mux_determine_rate_flags(hw, req, hw->init->flags);
> +}
> +
> +static const struct clk_ops atlantis_clk_mux_ops = {
> +	.get_parent = atlantis_clk_mux_get_parent,
> +	.set_parent = atlantis_clk_mux_set_parent,
> +	.determine_rate = atlantis_clk_mux_determine_rate,
> +};
> +
> +static void atlantis_clk_gate_endisable(struct clk_hw *hw, int enable)

Should this return an int? This looks like we should use this return
value below in atlantis_clk_gate_enable().

> +{
> +	struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);
> +	u32 val;
> +
> +	if (enable)
> +		val = gate->config.enable;
> +	else
> +		val = ~(gate->config.enable);
> +
> +	regmap_update_bits(gate->common.regmap, gate->config.reg_offset,
> +			   gate->config.enable, val);

This chunk could be simplified to use regmap_set_bits() and
regmap_clear_bits().

> +}
> +
> +static int atlantis_clk_gate_enable(struct clk_hw *hw)
> +{
> +	atlantis_clk_gate_endisable(hw, 1);
> +
> +	return 0;

Follow up from above. Any reason why the return value of
regmap_update_bits() in atlantis_clk_gate_endisable() is discarded?

I know it's not used below in the disable().

> +}
> +
> +static void atlantis_clk_gate_disable(struct clk_hw *hw)
> +{
> +	atlantis_clk_gate_endisable(hw, 0);
> +}
> +
> +static int atlantis_clk_gate_is_enabled(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);
> +	u32 val;
> +
> +	regmap_read(gate->common.regmap, gate->config.reg_offset, &val);
> +	val &= gate->config.enable;
> +
> +	return !!val;

regmap_test_bits() can simplify this.

> +}
> +
> +static const struct clk_ops atlantis_clk_gate_ops = {
> +	.enable = atlantis_clk_gate_enable,
> +	.disable = atlantis_clk_gate_disable,
> +	.is_enabled = atlantis_clk_gate_is_enabled,
> +};
> +
> +static unsigned long atlantis_clk_divider_recalc_rate(struct clk_hw *hw,
> +						      unsigned long parent_rate)
> +{
> +	struct atlantis_clk_divider *divider = hw_to_atlantis_clk_divider(hw);
> +	u32 val;
> +
> +	regmap_read(divider->common.regmap, divider->config.reg_offset, &val);
> +
> +	val >>= divider->config.shift;
> +	val &= ((1 << (divider->config.width)) - 1);

FIELD_GET() ?

> +
> +	return DIV_ROUND_UP_ULL((u64)parent_rate, val + 1);
> +}
> +
> +static const struct clk_ops atlantis_clk_divider_ops = {
> +	.recalc_rate = atlantis_clk_divider_recalc_rate,
> +};
> +
> +static unsigned long
> +atlantis_clk_fixed_factor_recalc_rate(struct clk_hw *hw,
> +				      unsigned long parent_rate)
> +{
> +	struct atlantis_clk_fixed_factor *factor =
> +		hw_to_atlantis_clk_fixed_factor(hw);
> +	unsigned long long rate;
> +
> +	rate = (unsigned long long)parent_rate * factor->config.mult;
> +	do_div(rate, factor->config.div);

newline

> +	return (unsigned long)rate;
> +}
> +
> +static const struct clk_ops atlantis_clk_fixed_factor_ops = {
> +	.recalc_rate = atlantis_clk_fixed_factor_recalc_rate,
> +};
> +
> +static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> +	u32 val, en_val, cg_val;
> +
> +	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> +	regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
> +	regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);
> +
> +	/* Check if PLL is powered on, locked, not bypassed and Gate clk is enabled */
> +	return !!(en_val & PLL_CFG_EN_BIT) && !!(val & PLL_CFG_LOCK_BIT) &&
> +	       (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
> +	       !(val & PLL_CFG_BYPASS_BIT);

Could regmap_test_bits() make this a bit cleaner?

> +}
> +
> +static int atlantis_clk_pll_enable(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> +	u32 val, en_val, cg_val;
> +	int ret;
> +
> +	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> +	regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
> +	regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);
> +
> +	/* Check if PLL is already enabled, locked, not bypassed and Gate clk is enabled */
> +	if ((en_val & PLL_CFG_EN_BIT) && (val & PLL_CFG_LOCK_BIT) &&
> +	    (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
> +	    !(val & PLL_CFG_BYPASS_BIT)) {

Same about regmap_test_bits() here.

> +		return 0;
> +	}
> +
> +	/* Step 1: Set bypass mode first */
> +	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
> +			   PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);
> +
> +	/* Step 2: Enable PLL (clear then set power bit) */
> +	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
> +			   PLL_CFG_EN_BIT, 0);
> +
> +	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
> +			   PLL_CFG_EN_BIT, PLL_CFG_EN_BIT);
> +
> +	/* Step 3: Wait for PLL lock */
> +	ret = regmap_read_poll_timeout(pll->common.regmap,
> +				       pll->config.reg_offset, val,
> +				       val & PLL_CFG_LOCK_BIT, 10,
> +				       PLL_BYPASS_WAIT_US);

Should the last two parameters be 
PLL_BYPASS_WAIT_US, PLL_LOCK_TIMEOUT_US instead of
10, PLL_BYPASS_WAIT_US?

> +	if (ret) {
> +		pr_err("PLL failed to lock within timeout\n");
> +		return ret;
> +	}
> +
> +	/* Step 4: Switch from bypass to PLL output */
> +	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
> +			   PLL_CFG_BYPASS_BIT, 0);
> +
> +	/* Enable Gate clk at PLL Output */
> +	return regmap_update_bits(pll->common.regmap, pll->config.cg_reg_offset,
> +				  pll->config.cg_reg_enable,
> +				  pll->config.cg_reg_enable);
> +}
> +
> +static void atlantis_clk_pll_disable(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> +
> +	/* Step 1: Switch to bypass mode before disabling */
> +	regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
> +			   PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);
> +	/* Step 2: Power down PLL */
> +	regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
> +			   PLL_CFG_EN_BIT, 0);
> +}
> +
> +static unsigned long atlantis_clk_pll_recalc_rate(struct clk_hw *hw,
> +						  unsigned long parent_rate)
> +{
> +	struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> +
> +	u32 val, refdiv, fbdiv, postdiv1, postdiv2;
> +	u64 fout;
> +
> +	regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> +
> +	if (val & PLL_CFG_BYPASS_BIT)
> +		return parent_rate;
> +
> +	refdiv = FIELD_GET(PLL_CFG_REFDIV_MASK, val);
> +	fbdiv = FIELD_GET(PLL_CFG_FBDIV_MASK, val);
> +	postdiv1 = FIELD_GET(PLL_CFG_POSTDIV1_MASK, val);
> +	postdiv2 = FIELD_GET(PLL_CFG_POSTDIV2_MASK, val);
> +
> +	if (!refdiv)
> +		refdiv = 1;
> +	if (!postdiv1)
> +		postdiv1 = 1;
> +	if (!postdiv2)
> +		postdiv2 = 1;
> +	if (!fbdiv)
> +		return 0;
> +
> +	fout = div64_u64((u64)parent_rate * fbdiv,
> +			 refdiv * postdiv1 * postdiv2);
> +
> +	return fout;
> +}
> +
> +static const struct clk_ops atlantis_clk_pll_ops = {
> +	.enable = atlantis_clk_pll_enable,
> +	.disable = atlantis_clk_pll_disable,
> +	.recalc_rate = atlantis_clk_pll_recalc_rate,
> +	.is_enabled = atlantis_clk_pll_is_enabled,
> +};
> +
> +static int atlantis_clk_gate_shared_enable(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_gate_shared *gate =
> +		hw_to_atlantis_clk_gate_shared(hw);
> +	bool need_enable;
> +	u32 reg;
> +
> +	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
> +	{
> +		need_enable = (*gate->config.share_count)++ == 0;
> +		if (need_enable) {
> +			regmap_read(gate->common.regmap,
> +				    gate->config.reg_offset, &reg);
> +			reg |= gate->config.enable;
> +			regmap_write(gate->common.regmap,
> +				     gate->config.reg_offset, reg);

Should this use regmap_set_bits()?

> +		}
> +	}
> +
> +	if (need_enable) {
> +		regmap_read(gate->common.regmap, gate->config.reg_offset, &reg);
> +
> +		if (!(reg & gate->config.enable)) {

regmap_test_bits() ?

> +			pr_warn("%s: gate enable %d failed to enable\n",
> +				clk_hw_get_name(hw), gate->config.enable);
> +			return -EIO;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void atlantis_clk_gate_shared_disable(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_gate_shared *gate =
> +		hw_to_atlantis_clk_gate_shared(hw);
> +	u32 reg;
> +
> +	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
> +	{
> +		if (WARN_ON(*gate->config.share_count == 0))
> +			return;
> +		if (--(*gate->config.share_count) > 0)
> +			return;
> +
> +		regmap_read(gate->common.regmap, gate->config.reg_offset, &reg);
> +		reg &= ~gate->config.enable;
> +		regmap_write(gate->common.regmap, gate->config.reg_offset, reg);

Should this use regmap_clear_bits()?

> +	}
> +}
> +
> +static int atlantis_clk_gate_shared_is_enabled(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_gate_shared *gate =
> +		hw_to_atlantis_clk_gate_shared(hw);
> +	u32 reg;
> +
> +	regmap_read(gate->common.regmap, gate->config.reg_offset, &reg);
> +
> +	return !!(reg & gate->config.enable);

regmap_test_bits() ?

> +}
> +
> +static void atlantis_clk_gate_shared_disable_unused(struct clk_hw *hw)
> +{
> +	struct atlantis_clk_gate_shared *gate =
> +		hw_to_atlantis_clk_gate_shared(hw);
> +
> +	u32 reg;
> +
> +	scoped_guard(spinlock_irqsave, gate->config.refcount_lock)
> +	{
> +		if (*gate->config.share_count == 0) {
> +			regmap_read(gate->common.regmap,
> +				    gate->config.reg_offset, &reg);
> +			reg &= ~gate->config.enable;
> +			regmap_write(gate->common.regmap,
> +				     gate->config.reg_offset, reg);

regmap_clear_bits() ?

> +		}
> +	}
> +}
> +
> +static const struct clk_ops atlantis_clk_gate_shared_ops = {
> +	.enable = atlantis_clk_gate_shared_enable,
> +	.disable = atlantis_clk_gate_shared_disable,
> +	.disable_unused = atlantis_clk_gate_shared_disable_unused,
> +	.is_enabled = atlantis_clk_gate_shared_is_enabled,
> +};
> +
> +#define ATLANTIS_PLL_CONFIG(_reg_offset, _en_reg_offset, _cg_reg_offset, \
> +			    _cg_reg_enable)                              \
> +	{                                                                \
> +		.reg_offset = (_reg_offset),                             \
> +		.en_reg_offset = (_en_reg_offset),                       \
> +		.cg_reg_offset = (_cg_reg_offset),                       \
> +		.cg_reg_enable = (_cg_reg_enable),                       \
> +	}
> +
> +#define ATLANTIS_PLL_DEFINE(_clkid, _name, _parent, _reg_offset,               \
> +			    _en_reg_offset, _cg_reg_offset, _cg_reg_enable,    \
> +			    _flags)                                            \
> +	static struct atlantis_clk_pll _name = {                               \
> +		.config = ATLANTIS_PLL_CONFIG(_reg_offset, _en_reg_offset,     \
> +					      _cg_reg_offset, _cg_reg_enable), \
> +		.common = { .clkid = _clkid,                                   \
> +			    .hw.init = CLK_HW_INIT_PARENTS_DATA(               \
> +				    #_name, _parent, &atlantis_clk_pll_ops,    \
> +				    _flags) },                                 \
> +	}
> +#define ATLANTIS_MUX_CONFIG(_shift, _width, _reg_offset)                    \
> +	{                                                                   \
> +		.shift = _shift, .width = _width, .reg_offset = _reg_offset \
> +	}
> +
> +#define ATLANTIS_MUX_DEFINE(_clkid, _name, _parents, _reg_offset, _shift,    \
> +			    _width, _flags)                                  \
> +	static struct atlantis_clk_mux _name = {                             \
> +		.config = ATLANTIS_MUX_CONFIG(_shift, _width, _reg_offset),  \
> +		.common = { .clkid = _clkid,                                 \
> +			    .hw.init = CLK_HW_INIT_PARENTS_DATA(             \
> +				    #_name, _parents, &atlantis_clk_mux_ops, \
> +				    _flags) }                                \
> +	}
> +
> +#define ATLANTIS_DIVIDER_CONFIG(_shift, _width, _flags, _reg_offset) \
> +	{                                                            \
> +		.shift = _shift, .width = _width, .flags = _flags,   \
> +		.reg_offset = _reg_offset                            \
> +	}
> +
> +#define ATLANTIS_DIVIDER_DEFINE(_clkid, _name, _parent, _reg_offset, _shift, \
> +				_width, _divflags, _flags)                   \
> +	static struct atlantis_clk_divider _name = {                         \
> +		.config = ATLANTIS_DIVIDER_CONFIG(_shift, _width, _divflags, \
> +						  _reg_offset),              \
> +		.common = { .clkid = _clkid,                                 \
> +			    .hw.init = CLK_HW_INIT_HW(                       \
> +				    #_name, &_parent.common.hw,              \
> +				    &atlantis_clk_divider_ops, _flags) }     \
> +	}
> +#define ATLANTIS_GATE_CONFIG(_enable, _reg_offset)           \
> +	{                                                    \
> +		.enable = _enable, .reg_offset = _reg_offset \
> +	}
> +
> +#define ATLANTIS_GATE_DEFINE(_clkid, _name, _parent, _reg_offset, _enable, \
> +			     _flags)                                       \
> +	static struct atlantis_clk_gate _name = {                          \
> +		.config = ATLANTIS_GATE_CONFIG(_enable, _reg_offset),      \
> +		.common = { .clkid = _clkid,                               \
> +			    .hw.init = CLK_HW_INIT_HW(                     \
> +				    #_name, &_parent.common.hw,            \
> +				    &atlantis_clk_gate_ops, _flags) }      \
> +	}
> +#define ATLANTIS_GATE_SHARED_CONFIG(_reg_offset, _enable, _share_count)      \
> +	{                                                                    \
> +		.reg_offset = _reg_offset, .enable = _enable,                \
> +		.share_count = _share_count, .refcount_lock = &refcount_lock \
> +	}
> +#define ATLANTIS_GATE_SHARED_DEFINE(_clkid, _name, _parent, _reg_offset,     \
> +				    _enable, _share_count, _flags)           \
> +	static struct atlantis_clk_gate_shared _name = {                     \
> +		.config = ATLANTIS_GATE_SHARED_CONFIG(_reg_offset, _enable,  \
> +						      _share_count),         \
> +		.common = { .clkid = _clkid,                                 \
> +			    .hw.init = CLK_HW_INIT_HW(                       \
> +				    #_name, &_parent.common.hw,              \
> +				    &atlantis_clk_gate_shared_ops, _flags) } \
> +	}
> +#define ATLANTIS_CLK_FIXED_FACTOR_DEFINE(_clkid, _name, _parent, _mult, _div, \
> +					 _flags)                              \
> +	static struct atlantis_clk_fixed_factor _name = {                     \
> +		.config = { .mult = _mult, .div = _div },                     \
> +		.common = { .clkid = _clkid,                                  \
> +			    .hw.init = CLK_HW_INIT_HW(                        \
> +				    #_name, &_parent.common.hw,               \
> +				    &atlantis_clk_fixed_factor_ops, _flags) } \
> +	}
> +
> +static DEFINE_SPINLOCK(refcount_lock); /* Lock for refcount value accesses */
> +
> +static const struct regmap_config atlantis_prcm_regmap_config = {
> +	.reg_bits = 32,
> +	.reg_stride = 4,
> +	.val_bits = 32,
> +	.max_register = 0xFFFC,
> +	.cache_type = REGCACHE_NONE,
> +};
> +
> +struct atlantis_prcm_data {
> +	struct clk_hw **hws;
> +	size_t num;
> +	const char *reset_name;
> +};
> +
> +static const struct clk_parent_data osc_24m_clk[] = {
> +	{ .index = 0 },
> +};
> +
> +ATLANTIS_PLL_DEFINE(CLK_RCPU_PLL, rcpu_pll_clk, osc_24m_clk, RCPU_PLL_CFG_REG,
> +		    PLL_RCPU_EN_REG, BUS_CG_REG, 0, /* No Gate Clk at Output */
> +		    CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL);
> +
> +static const struct clk_parent_data rcpu_root_parents[] = {
> +	{ .index = 0 },
> +	{ .hw = &rcpu_pll_clk.common.hw },
> +};
> +
> +ATLANTIS_MUX_DEFINE(CLK_RCPU_ROOT, rcpu_root_mux, rcpu_root_parents,
> +		    RCPU_DIV_CFG_REG, 0, 1, CLK_SET_RATE_NO_REPARENT);
> +
> +ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_DIV2, rcpu_div2_clk, rcpu_root_mux,
> +			RCPU_DIV_CFG_REG, 2, 4, 0, 0);
> +ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_DIV4, rcpu_div4_clk, rcpu_root_mux,
> +			RCPU_DIV_CFG_REG, 7, 4, 0, 0);
> +ATLANTIS_DIVIDER_DEFINE(CLK_RCPU_RTC, rcpu_rtc_clk, rcpu_div4_clk,
> +			RCPU_DIV_CFG_REG, 12, 6, 0, 0);
> +
> +ATLANTIS_GATE_DEFINE(CLK_SMNDMA0_ACLK, smndma0_aclk, rcpu_div2_clk,
> +		     RCPU_BLK_CG_REG, BIT(0), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SMNDMA1_ACLK, smndma1_aclk, rcpu_div2_clk,
> +		     RCPU_BLK_CG_REG, BIT(1), 0);
> +ATLANTIS_GATE_DEFINE(CLK_WDT0_PCLK, wdt0_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
> +		     BIT(2), 0);
> +ATLANTIS_GATE_DEFINE(CLK_WDT1_PCLK, wdt1_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
> +		     BIT(3), 0);
> +ATLANTIS_GATE_DEFINE(CLK_TIMER_PCLK, timer_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
> +		     BIT(4), 0);
> +ATLANTIS_GATE_DEFINE(CLK_PVTC_PCLK, pvtc_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
> +		     BIT(12), 0);
> +ATLANTIS_GATE_DEFINE(CLK_PMU_PCLK, pmu_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
> +		     BIT(13), 0);
> +ATLANTIS_GATE_DEFINE(CLK_MAILBOX_HCLK, mb_hclk, rcpu_div2_clk, RCPU_BLK_CG_REG,
> +		     BIT(14), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SEC_SPACC_HCLK, sec_spacc_hclk, rcpu_div2_clk,
> +		     RCPU_BLK_CG_REG, BIT(26), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SEC_OTP_HCLK, sec_otp_hclk, rcpu_div2_clk,
> +		     RCPU_BLK_CG_REG, BIT(28), 0);
> +ATLANTIS_GATE_DEFINE(CLK_TRNG_PCLK, trng_pclk, rcpu_div4_clk, RCPU_BLK_CG_REG,
> +		     BIT(29), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SEC_CRC_HCLK, sec_crc_hclk, rcpu_div2_clk,
> +		     RCPU_BLK_CG_REG, BIT(30), 0);
> +
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SMN_HCLK, rcpu_smn_hclk, rcpu_div2_clk, 1,
> +				 1, 0);
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_AHB0_HCLK, rcpu_ahb0_hclk, rcpu_div2_clk,
> +				 1, 1, 0);
> +
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SMN_PCLK, rcpu_smn_pclk, rcpu_div4_clk, 1,
> +				 1, 0);
> +
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SMN_CLK, rcpu_smn_clk, rcpu_root_mux, 1, 1,
> +				 0);
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_SCRATCHPAD_CLK, rcpu_scratchpad_aclk,
> +				 rcpu_root_mux, 1, 1, 0);
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_RCPU_CORE_CLK, rcpu_core_clk,
> +				 rcpu_root_mux, 1, 1, 0);
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_RCPU_ROM_CLK, rcpu_rom_aclk, rcpu_root_mux,
> +				 1, 1, 0);
> +
> +static struct atlantis_clk_fixed_factor
> +	otp_load_clk = { .config = { .mult = 1, .div = 1 },
> +			 .common = {
> +				 .clkid = CLK_OTP_LOAD_CLK,
> +				 .hw.init = CLK_HW_INIT_PARENTS_DATA(
> +					 "otp_load_clk", osc_24m_clk,
> +					 &atlantis_clk_fixed_factor_ops,
> +					 CLK_SET_RATE_NO_REPARENT),
> +			 } };
> +
> +ATLANTIS_PLL_DEFINE(CLK_NOC_PLL, nocc_pll_clk, osc_24m_clk,
> +		    RCPU_NOCC_PLL_CFG_REG, PLL_NOCC_EN_REG, BUS_CG_REG, BIT(0),
> +		    CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL);
> +
> +static const struct clk_parent_data nocc_mux_parents[] = {
> +	{ .index = 0 },
> +	{ .hw = &nocc_pll_clk.common.hw },
> +};
> +
> +ATLANTIS_MUX_DEFINE(CLK_NOCC_CLK, nocc_clk_mux, nocc_mux_parents,
> +		    RCPU_NOCC_CLK_CFG_REG, 0, 1, CLK_SET_RATE_NO_REPARENT);
> +
> +ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_DIV2, nocc_div2_clk, nocc_clk_mux,
> +			RCPU_NOCC_CLK_CFG_REG, 1, 4, 0, 0);
> +ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_DIV4, nocc_div4_clk, nocc_clk_mux,
> +			RCPU_NOCC_CLK_CFG_REG, 5, 4, 0, 0);
> +ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_RTC, nocc_rtc_clk, nocc_div4_clk,
> +			RCPU_NOCC_CLK_CFG_REG, 9, 6, 0, 0);
> +ATLANTIS_DIVIDER_DEFINE(CLK_NOCC_CAN, nocc_can_div, nocc_clk_mux,
> +			RCPU_NOCC_CLK_CFG_REG, 15, 4, 0, 0);
> +
> +static unsigned int refcnt_qspi;
> +ATLANTIS_GATE_SHARED_DEFINE(CLK_QSPI_SCLK, qspi_sclk, nocc_clk_mux,
> +			    LSIO_BLK_CG_REG, BIT(0), &refcnt_qspi, 0);
> +ATLANTIS_GATE_SHARED_DEFINE(CLK_QSPI_HCLK, qspi_hclk, nocc_div2_clk,
> +			    LSIO_BLK_CG_REG, BIT(0), &refcnt_qspi, 0);
> +ATLANTIS_GATE_DEFINE(CLK_I2C0_PCLK, i2c0_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(1), 0);
> +ATLANTIS_GATE_DEFINE(CLK_I2C1_PCLK, i2c1_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(2), 0);
> +ATLANTIS_GATE_DEFINE(CLK_I2C2_PCLK, i2c2_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(3), 0);
> +ATLANTIS_GATE_DEFINE(CLK_I2C3_PCLK, i2c3_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(4), 0);
> +ATLANTIS_GATE_DEFINE(CLK_I2C4_PCLK, i2c4_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(5), 0);
> +
> +ATLANTIS_GATE_DEFINE(CLK_UART0_PCLK, uart0_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(6), 0);
> +ATLANTIS_GATE_DEFINE(CLK_UART1_PCLK, uart1_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(7), 0);
> +ATLANTIS_GATE_DEFINE(CLK_UART2_PCLK, uart2_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(8), 0);
> +ATLANTIS_GATE_DEFINE(CLK_UART3_PCLK, uart3_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(9), 0);
> +ATLANTIS_GATE_DEFINE(CLK_UART4_PCLK, uart4_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(10), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SPI0_PCLK, spi0_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(11), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SPI1_PCLK, spi1_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(12), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SPI2_PCLK, spi2_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(13), 0);
> +ATLANTIS_GATE_DEFINE(CLK_SPI3_PCLK, spi3_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(14), 0);
> +ATLANTIS_GATE_DEFINE(CLK_GPIO_PCLK, gpio_pclk, nocc_div4_clk, LSIO_BLK_CG_REG,
> +		     BIT(15), 0);
> +
> +static unsigned int refcnt_can0;
> +ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN0_HCLK, can0_hclk, nocc_div2_clk,
> +			    LSIO_BLK_CG_REG, BIT(17), &refcnt_can0, 0);
> +ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN0_CLK, can0_clk, nocc_can_div,
> +			    LSIO_BLK_CG_REG, BIT(17), &refcnt_can0, 0);
> +
> +static unsigned int refcnt_can1;
> +ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN1_HCLK, can1_hclk, nocc_div2_clk,
> +			    LSIO_BLK_CG_REG, BIT(18), &refcnt_can1, 0);
> +ATLANTIS_GATE_SHARED_DEFINE(CLK_CAN1_CLK, can1_clk, nocc_can_div,
> +			    LSIO_BLK_CG_REG, BIT(18), &refcnt_can1, 0);
> +
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_CAN0_TIMER_CLK, can0_timer_clk,
> +				 nocc_rtc_clk, 1, 1, 0);
> +ATLANTIS_CLK_FIXED_FACTOR_DEFINE(CLK_CAN1_TIMER_CLK, can1_timer_clk,
> +				 nocc_rtc_clk, 1, 1, 0);
> +
> +static struct clk_hw *atlantis_rcpu_clks[] = {
> +	[CLK_RCPU_PLL]		= &rcpu_pll_clk.common.hw,
> +	[CLK_RCPU_ROOT]		= &rcpu_root_mux.common.hw,
> +	[CLK_RCPU_DIV2]		= &rcpu_div2_clk.common.hw,
> +	[CLK_RCPU_DIV4]		= &rcpu_div4_clk.common.hw,
> +	[CLK_RCPU_RTC]		= &rcpu_rtc_clk.common.hw,
> +	[CLK_SMNDMA0_ACLK]	= &smndma0_aclk.common.hw,
> +	[CLK_SMNDMA1_ACLK]	= &smndma1_aclk.common.hw,
> +	[CLK_WDT0_PCLK]		= &wdt0_pclk.common.hw,
> +	[CLK_WDT1_PCLK]		= &wdt1_pclk.common.hw,
> +	[CLK_TIMER_PCLK]	= &timer_pclk.common.hw,
> +	[CLK_PVTC_PCLK]		= &pvtc_pclk.common.hw,
> +	[CLK_PMU_PCLK]		= &pmu_pclk.common.hw,
> +	[CLK_MAILBOX_HCLK]	= &mb_hclk.common.hw,
> +	[CLK_SEC_SPACC_HCLK]	= &sec_spacc_hclk.common.hw,
> +	[CLK_SEC_OTP_HCLK]	= &sec_otp_hclk.common.hw,
> +	[CLK_TRNG_PCLK]		= &trng_pclk.common.hw,
> +	[CLK_SEC_CRC_HCLK]	= &sec_crc_hclk.common.hw,
> +	[CLK_SMN_HCLK]		= &rcpu_smn_hclk.common.hw,
> +	[CLK_AHB0_HCLK]		= &rcpu_ahb0_hclk.common.hw,
> +	[CLK_SMN_PCLK]		= &rcpu_smn_pclk.common.hw,
> +	[CLK_SMN_CLK]		= &rcpu_smn_clk.common.hw,
> +	[CLK_SCRATCHPAD_CLK]	= &rcpu_scratchpad_aclk.common.hw,
> +	[CLK_RCPU_CORE_CLK]	= &rcpu_core_clk.common.hw,
> +	[CLK_RCPU_ROM_CLK]	= &rcpu_rom_aclk.common.hw,
> +	[CLK_OTP_LOAD_CLK]	= &otp_load_clk.common.hw,
> +	[CLK_NOC_PLL]		= &nocc_pll_clk.common.hw,
> +	[CLK_NOCC_CLK]		= &nocc_clk_mux.common.hw,
> +	[CLK_NOCC_DIV2]		= &nocc_div2_clk.common.hw,
> +	[CLK_NOCC_DIV4]		= &nocc_div4_clk.common.hw,
> +	[CLK_NOCC_RTC]		= &nocc_rtc_clk.common.hw,
> +	[CLK_NOCC_CAN]		= &nocc_can_div.common.hw,
> +	[CLK_QSPI_SCLK]		= &qspi_sclk.common.hw,
> +	[CLK_QSPI_HCLK]		= &qspi_hclk.common.hw,
> +	[CLK_I2C0_PCLK]		= &i2c0_pclk.common.hw,
> +	[CLK_I2C1_PCLK]		= &i2c1_pclk.common.hw,
> +	[CLK_I2C2_PCLK]		= &i2c2_pclk.common.hw,
> +	[CLK_I2C3_PCLK]		= &i2c3_pclk.common.hw,
> +	[CLK_I2C4_PCLK]		= &i2c4_pclk.common.hw,
> +	[CLK_UART0_PCLK]	= &uart0_pclk.common.hw,
> +	[CLK_UART1_PCLK]	= &uart1_pclk.common.hw,
> +	[CLK_UART2_PCLK]	= &uart2_pclk.common.hw,
> +	[CLK_UART3_PCLK]	= &uart3_pclk.common.hw,
> +	[CLK_UART4_PCLK]	= &uart4_pclk.common.hw,
> +	[CLK_SPI0_PCLK]		= &spi0_pclk.common.hw,
> +	[CLK_SPI1_PCLK]		= &spi1_pclk.common.hw,
> +	[CLK_SPI2_PCLK]		= &spi2_pclk.common.hw,
> +	[CLK_SPI3_PCLK]		= &spi3_pclk.common.hw,
> +	[CLK_GPIO_PCLK]		= &gpio_pclk.common.hw,
> +	[CLK_CAN0_HCLK]		= &can0_hclk.common.hw,
> +	[CLK_CAN0_CLK]		= &can0_clk.common.hw,
> +	[CLK_CAN1_HCLK]		= &can1_hclk.common.hw,
> +	[CLK_CAN1_CLK]		= &can1_clk.common.hw,
> +	[CLK_CAN0_TIMER_CLK]	= &can0_timer_clk.common.hw,
> +	[CLK_CAN1_TIMER_CLK]	= &can1_timer_clk.common.hw,
> +};
> +
> +static const struct atlantis_prcm_data atlantis_prcm_rcpu_data = {
> +	.hws = atlantis_rcpu_clks,
> +	.num = ARRAY_SIZE(atlantis_rcpu_clks),
> +	.reset_name = "rcpu-reset"
> +};
> +
> +static int atlantis_prcm_clocks_register(struct device *dev,
> +					 struct regmap *regmap,
> +					 const struct atlantis_prcm_data *data)
> +{
> +	struct clk_hw_onecell_data *clk_data;
> +	int i, ret;
> +	size_t num_clks = data->num;
> +
> +	clk_data = devm_kzalloc(dev, struct_size(clk_data, hws, data->num),
> +				GFP_KERNEL);
> +	if (!clk_data)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < data->num; i++) {
> +		struct clk_hw *hw = data->hws[i];
> +		struct atlantis_clk_common *common =
> +			hw_to_atlantis_clk_common(hw);
> +		common->regmap = regmap;
> +
> +		ret = devm_clk_hw_register(dev, hw);
> +

Remove newline

> +		if (ret)
> +			return ret;
> +
> +		clk_data->hws[common->clkid] = hw;
> +	}
> +
> +	clk_data->num = num_clks;
> +
> +	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
> +
> +	return ret;

These 3 lines can be simplified to return devm_..();

Brian


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

* Re: [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver
  2026-03-03 22:32   ` Brian Masney
@ 2026-03-04 16:40     ` Anirudh Srinivasan
       [not found]       ` <CAEev2e9fEsFqN5b4sAT9cbjVAvJEPvqxoqy74e5mG8K10xBQdg@mail.gmail.com>
  0 siblings, 1 reply; 8+ messages in thread
From: Anirudh Srinivasan @ 2026-03-04 16:40 UTC (permalink / raw)
  To: Brian Masney
  Cc: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Philipp Zabel,
	linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, Krzysztof Kozlowski

Hello Brian,

On Tue, Mar 3, 2026 at 4:32 PM Brian Masney <bmasney@redhat.com> wrote:
>
> Hi Anirudh,
>
> Thanks for the patch. A few minor comments below with some minor
> nitpicks, additional places to use FIELD_GET(), plus some suggestions
> for additional regmap helpers to use.
>
> On Tue, Mar 03, 2026 at 11:36:09AM -0600, Anirudh Srinivasan wrote:
> > Add driver for clock controller in Tenstorrent Atlantis SoC. This version
> > of the driver covers clocks from RCPU subsystem.
> >
> > 5 types of clocks generated by this controller: PLLs (PLLs
> > with bypass functionality and an additional Gate clk at output), Shared
> > Gates (Multiple Gate clks that share an enable bit), standard Muxes,
> > Dividers and Gates. All clocks are implemented using custom clk ops and
> > use the regmap interface associated with the syscon. All clocks are derived
> > from a 24 Mhz oscillator.
> >
> > The reset controller is also setup as an auxiliary device of the clock
> > controller.
> >
> > Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>

> > +
> > +static u8 atlantis_clk_mux_get_parent(struct clk_hw *hw)
> > +{
> > +     struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
> > +     u32 val;
> > +
> > +     regmap_read(mux->common.regmap, mux->config.reg_offset, &val);
> > +     val >>= mux->config.shift;
> > +     val &= (BIT(mux->config.width) - 1);
> > +
> > +     return val;
>
> FIELD_GET() ?

<copying another snippet from below>

> > +static unsigned long atlantis_clk_divider_recalc_rate(struct clk_hw *hw,
> > +                                                   unsigned long parent_rate)
> > +{
> > +     struct atlantis_clk_divider *divider = hw_to_atlantis_clk_divider(hw);
> > +     u32 val;
> > +
> > +     regmap_read(divider->common.regmap, divider->config.reg_offset, &val);
> > +
> > +     val >>= divider->config.shift;
> > +     val &= ((1 << (divider->config.width)) - 1);
>
> FIELD_GET() ?

Most uses of FIELD_GET I see have the shift and width for the mask be
static compile time constants, which isn't the case here. I don't
think it makes sense to use it here. You could manually compute the
mask and pass that to FIELD_GET, but you end up with more lines of
code at that point

>
> > +}
> > +
> > +static int atlantis_clk_mux_set_parent(struct clk_hw *hw, u8 index)
> > +{
> > +     struct atlantis_clk_mux *mux = hw_to_atlantis_clk_mux(hw);
> > +     u32 val = index;
> > +
> > +     return regmap_update_bits(mux->common.regmap, mux->config.reg_offset,
> > +                               (BIT(mux->config.width) - 1)
> > +                                       << mux->config.shift,
>
> This doesn't make the line that much longer, and is nicer to read:
>
>     (BIT(mux->config.width) - 1) << mux->config.shift,

Ack

> > +static void atlantis_clk_gate_endisable(struct clk_hw *hw, int enable)
>
> Should this return an int? This looks like we should use this return
> value below in atlantis_clk_gate_enable().
>
> > +{
> > +     struct atlantis_clk_gate *gate = hw_to_atlantis_clk_gate(hw);
> > +     u32 val;
> > +
> > +     if (enable)
> > +             val = gate->config.enable;
> > +     else
> > +             val = ~(gate->config.enable);
> > +
> > +     regmap_update_bits(gate->common.regmap, gate->config.reg_offset,
> > +                        gate->config.enable, val);
>
> This chunk could be simplified to use regmap_set_bits() and
> regmap_clear_bits().
>
> > +}
> > +
> > +static int atlantis_clk_gate_enable(struct clk_hw *hw)
> > +{
> > +     atlantis_clk_gate_endisable(hw, 1);
> > +
> > +     return 0;
>
> Follow up from above. Any reason why the return value of
> regmap_update_bits() in atlantis_clk_gate_endisable() is discarded?
>
> I know it's not used below in the disable().

For some reason, clk_gate_enable returns an int (I guess we care about
errors here), while clk_gate_disable doesn't return anything (but we
don't here?). I've updated them (and clk_get_endisable) based on your
suggestions so that the return code from regmap_{set|clear}_bits is
passed through.

> > +
> > +static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
> > +{
> > +     struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> > +     u32 val, en_val, cg_val;
> > +
> > +     regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> > +     regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
> > +     regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);
> > +
> > +     /* Check if PLL is powered on, locked, not bypassed and Gate clk is enabled */
> > +     return !!(en_val & PLL_CFG_EN_BIT) && !!(val & PLL_CFG_LOCK_BIT) &&
> > +            (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
> > +            !(val & PLL_CFG_BYPASS_BIT);
>
> Could regmap_test_bits() make this a bit cleaner?
>
> > +}
> > +
> > +static int atlantis_clk_pll_enable(struct clk_hw *hw)
> > +{
> > +     struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> > +     u32 val, en_val, cg_val;
> > +     int ret;
> > +
> > +     regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> > +     regmap_read(pll->common.regmap, pll->config.en_reg_offset, &en_val);
> > +     regmap_read(pll->common.regmap, pll->config.cg_reg_offset, &cg_val);
> > +
> > +     /* Check if PLL is already enabled, locked, not bypassed and Gate clk is enabled */
> > +     if ((en_val & PLL_CFG_EN_BIT) && (val & PLL_CFG_LOCK_BIT) &&
> > +         (!pll->config.cg_reg_enable || (cg_val & pll->config.cg_reg_enable)) &&
> > +         !(val & PLL_CFG_BYPASS_BIT)) {
>
> Same about regmap_test_bits() here.

These instances have it reading 3 different registers (unlike almost
all the other examples that just read one) and testing bits across
them. But I guess it should be possible to use test_bits here too. I
will update them.

>
> > +             return 0;
> > +     }
> > +
> > +     /* Step 1: Set bypass mode first */
> > +     regmap_update_bits(pll->common.regmap, pll->config.reg_offset,
> > +                        PLL_CFG_BYPASS_BIT, PLL_CFG_BYPASS_BIT);
> > +
> > +     /* Step 2: Enable PLL (clear then set power bit) */
> > +     regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
> > +                        PLL_CFG_EN_BIT, 0);
> > +
> > +     regmap_update_bits(pll->common.regmap, pll->config.en_reg_offset,
> > +                        PLL_CFG_EN_BIT, PLL_CFG_EN_BIT);
> > +
> > +     /* Step 3: Wait for PLL lock */
> > +     ret = regmap_read_poll_timeout(pll->common.regmap,
> > +                                    pll->config.reg_offset, val,
> > +                                    val & PLL_CFG_LOCK_BIT, 10,
> > +                                    PLL_BYPASS_WAIT_US);
>
> Should the last two parameters be
> PLL_BYPASS_WAIT_US, PLL_LOCK_TIMEOUT_US instead of
> 10, PLL_BYPASS_WAIT_US?

Yes, thanks for catching this.

> > +
> > +     for (i = 0; i < data->num; i++) {
> > +             struct clk_hw *hw = data->hws[i];
> > +             struct atlantis_clk_common *common =
> > +                     hw_to_atlantis_clk_common(hw);
> > +             common->regmap = regmap;
> > +
> > +             ret = devm_clk_hw_register(dev, hw);
> > +
>
> Remove newline
>
> > +             if (ret)
> > +                     return ret;
> > +
> > +             clk_data->hws[common->clkid] = hw;
> > +     }
> > +
> > +     clk_data->num = num_clks;
> > +
> > +     ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_data);
> > +
> > +     return ret;
>
> These 3 lines can be simplified to return devm_..();

Done

Thanks for your comments. I've incorporated  the changes you suggested
on using regmap helpers. I left some comments on the use of FIELD_GET.
Let me know if you have any other suggestions. I'll send an updated
version by the end of this week.

>
> Brian
>

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

* Re: [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver
       [not found]       ` <CAEev2e9fEsFqN5b4sAT9cbjVAvJEPvqxoqy74e5mG8K10xBQdg@mail.gmail.com>
@ 2026-03-04 22:21         ` Brian Masney
  2026-03-04 23:43           ` Anirudh Srinivasan
  0 siblings, 1 reply; 8+ messages in thread
From: Brian Masney @ 2026-03-04 22:21 UTC (permalink / raw)
  To: Anirudh Srinivasan
  Cc: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Philipp Zabel,
	linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, Krzysztof Kozlowski

Hi Anirudh,

On Wed, Mar 04, 2026 at 11:53:38AM -0600, Anirudh Srinivasan wrote:
> Sorry for the follow up, but wanted to run something by you.
> 
> On Wed, Mar 4, 2026 at 10:40 AM Anirudh Srinivasan <
> asrinivasan@oss.tenstorrent.com> wrote:
> >
> > Hello Brian,
> >
> > On Tue, Mar 3, 2026 at 4:32 PM Brian Masney <bmasney@redhat.com> wrote:
> > >
> > > Hi Anirudh,
> > >
> > > Thanks for the patch. A few minor comments below with some minor
> > > nitpicks, additional places to use FIELD_GET(), plus some suggestions
> > > for additional regmap helpers to use.
> > >
> 
> 
> > > > +
> > > > +static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
> > > > +{
> > > > +     struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> > > > +     u32 val, en_val, cg_val;
> > > > +
> > > > +     regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> > > > +     regmap_read(pll->common.regmap, pll->config.en_reg_offset,
> &en_val);
> > > > +     regmap_read(pll->common.regmap, pll->config.cg_reg_offset,
> &cg_val);
> > > > +
> > > > +     /* Check if PLL is powered on, locked, not bypassed and Gate
> clk is enabled */
> > > > +     return !!(en_val & PLL_CFG_EN_BIT) && !!(val &
> PLL_CFG_LOCK_BIT) &&
> > > > +            (!pll->config.cg_reg_enable || (cg_val &
> pll->config.cg_reg_enable)) &&
> > > > +            !(val & PLL_CFG_BYPASS_BIT);
> > >
> > > Could regmap_test_bits() make this a bit cleaner?
> > >
> > > > +}
> > > > +
> > > > +static int atlantis_clk_pll_enable(struct clk_hw *hw)
> > > > +{
> > > > +     struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> > > > +     u32 val, en_val, cg_val;
> > > > +     int ret;
> > > > +
> > > > +     regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> > > > +     regmap_read(pll->common.regmap, pll->config.en_reg_offset,
> &en_val);
> > > > +     regmap_read(pll->common.regmap, pll->config.cg_reg_offset,
> &cg_val);
> > > > +
> > > > +     /* Check if PLL is already enabled, locked, not bypassed and
> Gate clk is enabled */
> > > > +     if ((en_val & PLL_CFG_EN_BIT) && (val & PLL_CFG_LOCK_BIT) &&
> > > > +         (!pll->config.cg_reg_enable || (cg_val &
> pll->config.cg_reg_enable)) &&
> > > > +         !(val & PLL_CFG_BYPASS_BIT)) {
> > >
> > > Same about regmap_test_bits() here.
> >
> > These instances have it reading 3 different registers (unlike almost
> > all the other examples that just read one) and testing bits across
> > them. But I guess it should be possible to use test_bits here too. I
> > will update them.
> 
> This ends up becoming like this.
> 
> static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
> 
> {
> 
>         struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> 
> 
> 
> 
>         /* Check if PLL is powered on, locked, not bypassed and Gate clk is
> enabled */
>         return regmap_test_bits(pll->common.regmap, pll->config.reg_offset,
> PLL_CFG_LOCK_BIT) &&
> 
>                 regmap_test_bits(pll->common.regmap,
> pll->config.en_reg_offset, PLL_CFG_EN_BIT) &&
> 
>                 regmap_test_bits(pll->common.regmap,
> pll->config.cg_reg_offset, pll->config.cg_reg_enable) &&
> 
>                 !regmap_test_bits(pll->common.regmap,
> pll->config.reg_offset, PLL_CFG_BYPASS_BIT);
> 
> }
> 
> We can't use a single call of regmap_test_bits to the
> pll->config.reg_offset register cause we need to check if PLL_CFG_LOCK_BIT
> is set and PLL_CFG_BYPASS_BIT is unset. We end up needing to make 2 reads
> to that register.
> 
> Any thoughts on whether I should still be using regmap_test_bits here?

If it's not looking good in practice once you implement it, then just
fall back to the older behavior. It was just a suggestion.

I haven't used this yet, however there are also the regmap_field_xxx()
helpers that are also available. drivers/clk/mstar/clk-msc313-mpll.c
uses these helpers. I don't know if it would help to simplify your code.

Brian


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

* Re: [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver
  2026-03-04 22:21         ` Brian Masney
@ 2026-03-04 23:43           ` Anirudh Srinivasan
  0 siblings, 0 replies; 8+ messages in thread
From: Anirudh Srinivasan @ 2026-03-04 23:43 UTC (permalink / raw)
  To: Brian Masney
  Cc: Drew Fustini, Joel Stanley, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Michael Turquette, Stephen Boyd, Philipp Zabel,
	linux-riscv, devicetree, linux-kernel, linux-clk, joel, fustini,
	mpe, mpe, npiggin, agross, agross, Krzysztof Kozlowski

Hi Brian,

On Wed, Mar 4, 2026 at 4:21 PM Brian Masney <bmasney@redhat.com> wrote:
>
> Hi Anirudh,
>
> On Wed, Mar 04, 2026 at 11:53:38AM -0600, Anirudh Srinivasan wrote:
> > Sorry for the follow up, but wanted to run something by you.
> >
> > On Wed, Mar 4, 2026 at 10:40 AM Anirudh Srinivasan <
> > asrinivasan@oss.tenstorrent.com> wrote:
> > >
> > > Hello Brian,
> > >
> > > On Tue, Mar 3, 2026 at 4:32 PM Brian Masney <bmasney@redhat.com> wrote:
> > > >
> > > > Hi Anirudh,
> > > >
> > > > Thanks for the patch. A few minor comments below with some minor
> > > > nitpicks, additional places to use FIELD_GET(), plus some suggestions
> > > > for additional regmap helpers to use.
> > > >
> >
> >
> > > > > +
> > > > > +static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
> > > > > +{
> > > > > +     struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> > > > > +     u32 val, en_val, cg_val;
> > > > > +
> > > > > +     regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> > > > > +     regmap_read(pll->common.regmap, pll->config.en_reg_offset,
> > &en_val);
> > > > > +     regmap_read(pll->common.regmap, pll->config.cg_reg_offset,
> > &cg_val);
> > > > > +
> > > > > +     /* Check if PLL is powered on, locked, not bypassed and Gate
> > clk is enabled */
> > > > > +     return !!(en_val & PLL_CFG_EN_BIT) && !!(val &
> > PLL_CFG_LOCK_BIT) &&
> > > > > +            (!pll->config.cg_reg_enable || (cg_val &
> > pll->config.cg_reg_enable)) &&
> > > > > +            !(val & PLL_CFG_BYPASS_BIT);
> > > >
> > > > Could regmap_test_bits() make this a bit cleaner?
> > > >
> > > > > +}
> > > > > +
> > > > > +static int atlantis_clk_pll_enable(struct clk_hw *hw)
> > > > > +{
> > > > > +     struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> > > > > +     u32 val, en_val, cg_val;
> > > > > +     int ret;
> > > > > +
> > > > > +     regmap_read(pll->common.regmap, pll->config.reg_offset, &val);
> > > > > +     regmap_read(pll->common.regmap, pll->config.en_reg_offset,
> > &en_val);
> > > > > +     regmap_read(pll->common.regmap, pll->config.cg_reg_offset,
> > &cg_val);
> > > > > +
> > > > > +     /* Check if PLL is already enabled, locked, not bypassed and
> > Gate clk is enabled */
> > > > > +     if ((en_val & PLL_CFG_EN_BIT) && (val & PLL_CFG_LOCK_BIT) &&
> > > > > +         (!pll->config.cg_reg_enable || (cg_val &
> > pll->config.cg_reg_enable)) &&
> > > > > +         !(val & PLL_CFG_BYPASS_BIT)) {
> > > >
> > > > Same about regmap_test_bits() here.
> > >
> > > These instances have it reading 3 different registers (unlike almost
> > > all the other examples that just read one) and testing bits across
> > > them. But I guess it should be possible to use test_bits here too. I
> > > will update them.
> >
> > This ends up becoming like this.
> >
> > static int atlantis_clk_pll_is_enabled(struct clk_hw *hw)
> >
> > {
> >
> >         struct atlantis_clk_pll *pll = hw_to_atlantis_pll(hw);
> >
> >
> >
> >
> >         /* Check if PLL is powered on, locked, not bypassed and Gate clk is
> > enabled */
> >         return regmap_test_bits(pll->common.regmap, pll->config.reg_offset,
> > PLL_CFG_LOCK_BIT) &&
> >
> >                 regmap_test_bits(pll->common.regmap,
> > pll->config.en_reg_offset, PLL_CFG_EN_BIT) &&
> >
> >                 regmap_test_bits(pll->common.regmap,
> > pll->config.cg_reg_offset, pll->config.cg_reg_enable) &&
> >
> >                 !regmap_test_bits(pll->common.regmap,
> > pll->config.reg_offset, PLL_CFG_BYPASS_BIT);
> >
> > }
> >
> > We can't use a single call of regmap_test_bits to the
> > pll->config.reg_offset register cause we need to check if PLL_CFG_LOCK_BIT
> > is set and PLL_CFG_BYPASS_BIT is unset. We end up needing to make 2 reads
> > to that register.
> >
> > Any thoughts on whether I should still be using regmap_test_bits here?
>
> If it's not looking good in practice once you implement it, then just
> fall back to the older behavior. It was just a suggestion.

Understood.

>
> I haven't used this yet, however there are also the regmap_field_xxx()
> helpers that are also available. drivers/clk/mstar/clk-msc313-mpll.c
> uses these helpers. I don't know if it would help to simplify your code.

Doesn't look like it'll help simplify things to me, so I'll keep things as is.

>
> Brian
>

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

end of thread, other threads:[~2026-03-04 23:43 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-03 17:36 [PATCH v7 0/3] Add Tenstorrent Atlantis Clock/Reset Controller Anirudh Srinivasan
2026-03-03 17:36 ` [PATCH v7 1/3] dt-bindings: clk: tenstorrent: Add tenstorrent,atlantis-prcm-rcpu Anirudh Srinivasan
2026-03-03 17:36 ` [PATCH v7 2/3] reset: tenstorrent: Add reset controller for Atlantis Anirudh Srinivasan
2026-03-03 17:36 ` [PATCH v7 3/3] clk: tenstorrent: Add Atlantis clock controller driver Anirudh Srinivasan
2026-03-03 22:32   ` Brian Masney
2026-03-04 16:40     ` Anirudh Srinivasan
     [not found]       ` <CAEev2e9fEsFqN5b4sAT9cbjVAvJEPvqxoqy74e5mG8K10xBQdg@mail.gmail.com>
2026-03-04 22:21         ` Brian Masney
2026-03-04 23:43           ` Anirudh Srinivasan

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