mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/9] google: Add support for Laguna CPM
@ 2026-10-26 21:43 Brian Norris
  2026-10-26 21:43 ` [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array Brian Norris
                   ` (9 more replies)
  0 siblings, 10 replies; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Central Power Manager (CPM) is a co-processor on the Google Tensor
SoCs, as used on Pixel phones. CPM supports power-management and a
number of other features.

The Application Processor communicates with CPM over a pair of mailbox
channels. CPM provides clock, power-domain, reset, and interrupt
services.

This series adds device tree bindings, DTS, and drivers to support CPM,
and several of the basic services provided by CPM. These services are a
minimal set needed to bootstrap many other components on the SoC --
e.g., I have a working proof-of-concept PCIe driver running on top of
this series + pinctrl (not included here). CPM provides some other
components not yet included in this series (e.g., PMIC, RTC).

This series depends on the bindings and mailbox-payload-definitions from
this series:

  https://lore.kernel.org/all/20261023235408.3063350-1-dianders@chromium.org/
  [PATCH v2 0/4] mailbox: Improve the mbox core then introduce the goog-mba driver

So this series cannot be applied as-is yet. However, v1 of that series
was under review for about 3 months, so I'd like to at least:

  1) demonstrate its clients (this came up at
     https://lore.kernel.org/all/CAD=FV=VW2h1zWDYWQ6LknX3O+3=EY665jde35Ly+UQzuscXy=Q@mail.gmail.com/,
     for one); and

  2) start review for anything that isn't significantly impacted by
     potential changes in the above mailbox series.

With luck, the general design and DT bindings don't need to change, but
even if so, I expect any changes should not significantly affect:

 (a) the CPM DT binding or DTS (apart from #mbox-cells)

 (b) the CPM interface (cpm_request_client(), cpm_send_message())

 (c) any of the CPM clients (irqchip, reset, clk, pmdomain)

Thus, I think this falls somewhere between "PATCH" and "RFC".

This series has been tested on a Pixel 10 Pro XL (a.k.a., Mustang) both
as-is (plus the aforementioned mailbox patches), and also with minimal
additional local patches to get other components running on top
(pinctrl, PCIe, RTC), to prove out some more realistic use cases.

- Brian


Brian Norris (9):
  arm64: dts: google: Add Laguna CPM mailbox array
  dt-bindings: soc: google: Add Google Tensor CPM binding
  soc: google: Add CPM over MailBox Array driver
  pmdomain: google: Add CPM power-domain driver
  clk: google: Add CPM clock driver
  reset: google: Add CPM reset driver
  irqchip/google-cpm: Add CPM IRQ driver
  arm64: dts: google: Add Laguna CPM node
  MAINTAINERS: Add entries for CPM drivers used on Tensor SoCs

 .../bindings/soc/google/google,lga-cpm.yaml   |  74 ++
 MAINTAINERS                                   |  14 +-
 arch/arm64/boot/dts/google/lga.dtsi           |  50 +
 drivers/clk/Kconfig                           |   1 +
 drivers/clk/Makefile                          |   1 +
 drivers/clk/google/Kconfig                    |  13 +
 drivers/clk/google/Makefile                   |   3 +
 drivers/clk/google/clk-cpm-internal.h         |  35 +
 drivers/clk/google/clk-cpm-lga.c              | 271 +++++
 drivers/clk/google/clk-cpm.c                  | 573 +++++++++++
 drivers/irqchip/Kconfig                       |  10 +
 drivers/irqchip/Makefile                      |   1 +
 drivers/irqchip/irq-google-cpm.c              | 123 +++
 drivers/pmdomain/Kconfig                      |   1 +
 drivers/pmdomain/Makefile                     |   1 +
 drivers/pmdomain/google/Kconfig               |  14 +
 drivers/pmdomain/google/Makefile              |   2 +
 drivers/pmdomain/google/cpm-power-lga.h       | 467 +++++++++
 drivers/pmdomain/google/cpm-power-trace.h     |  80 ++
 drivers/pmdomain/google/cpm-power.c           | 637 ++++++++++++
 drivers/pmdomain/google/lpb_service.h         | 200 ++++
 drivers/reset/Kconfig                         |   1 +
 drivers/reset/Makefile                        |   1 +
 drivers/reset/google/Kconfig                  |  11 +
 drivers/reset/google/Makefile                 |   2 +
 drivers/reset/google/reset-cpm.c              | 367 +++++++
 drivers/soc/Kconfig                           |   1 +
 drivers/soc/Makefile                          |   1 +
 drivers/soc/google/Kconfig                    |  13 +
 drivers/soc/google/Makefile                   |   3 +
 drivers/soc/google/google-cpm-mba-trace.h     | 214 ++++
 drivers/soc/google/google-cpm-mba.c           | 964 ++++++++++++++++++
 drivers/soc/google/google-cpm-mba.h           |  30 +
 include/dt-bindings/clock/google,lga-cpm.h    | 191 ++++
 .../interrupt-controller/irq-cpm-google.h     |  16 +
 include/dt-bindings/power/google,lga-cpm.h    |  79 ++
 .../dt-bindings/reset/google,lga-cpm-reset.h  | 175 ++++
 include/dt-bindings/soc/google,lga-lpcm.h     |  54 +
 include/soc/google/goog_cpm_service_ids.h     |  84 ++
 include/soc/google/google-cpm-mba.h           | 147 +++
 40 files changed, 4923 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml
 create mode 100644 drivers/clk/google/Kconfig
 create mode 100644 drivers/clk/google/Makefile
 create mode 100644 drivers/clk/google/clk-cpm-internal.h
 create mode 100644 drivers/clk/google/clk-cpm-lga.c
 create mode 100644 drivers/clk/google/clk-cpm.c
 create mode 100644 drivers/irqchip/irq-google-cpm.c
 create mode 100644 drivers/pmdomain/google/Kconfig
 create mode 100644 drivers/pmdomain/google/Makefile
 create mode 100644 drivers/pmdomain/google/cpm-power-lga.h
 create mode 100644 drivers/pmdomain/google/cpm-power-trace.h
 create mode 100644 drivers/pmdomain/google/cpm-power.c
 create mode 100644 drivers/pmdomain/google/lpb_service.h
 create mode 100644 drivers/reset/google/Kconfig
 create mode 100644 drivers/reset/google/Makefile
 create mode 100644 drivers/reset/google/reset-cpm.c
 create mode 100644 drivers/soc/google/Kconfig
 create mode 100644 drivers/soc/google/Makefile
 create mode 100644 drivers/soc/google/google-cpm-mba-trace.h
 create mode 100644 drivers/soc/google/google-cpm-mba.c
 create mode 100644 drivers/soc/google/google-cpm-mba.h
 create mode 100644 include/dt-bindings/clock/google,lga-cpm.h
 create mode 100644 include/dt-bindings/interrupt-controller/irq-cpm-google.h
 create mode 100644 include/dt-bindings/power/google,lga-cpm.h
 create mode 100644 include/dt-bindings/reset/google,lga-cpm-reset.h
 create mode 100644 include/dt-bindings/soc/google,lga-lpcm.h
 create mode 100644 include/soc/google/goog_cpm_service_ids.h
 create mode 100644 include/soc/google/google-cpm-mba.h

-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 13:10   ` Krzysztof Kozlowski
  2026-10-26 21:43 ` [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding Brian Norris
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

These are the request and response channels used for communicating with
the Central Power Manager (CPM) found on the Google Tensor SoC known as
Laguna.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---
Based on WIP bindings from:

  https://lore.kernel.org/all/20261023165211.v2.3.Ia186a29119b4b5c409815b755e0c2c3965b0495f@changeid/
  [PATCH v2 3/4] dt-bindings: mailbox: goog-mba: Add goog-mba mailbox bindings

which is part of this series:

  https://lore.kernel.org/all/20261023235408.3063350-1-dianders@chromium.org/
  [PATCH v2 0/4] mailbox: Improve the mbox core then introduce the goog-mba driver

The binding is still under review, but I include this anyway to help
prove out a working solution. This patch should not be accepted until
the binding is settled.

 arch/arm64/boot/dts/google/lga.dtsi | 30 +++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/google/lga.dtsi b/arch/arm64/boot/dts/google/lga.dtsi
index 3e670e524c4c..6ceb9ea6a100 100644
--- a/arch/arm64/boot/dts/google/lga.dtsi
+++ b/arch/arm64/boot/dts/google/lga.dtsi
@@ -365,6 +365,36 @@ soc: soc@0 {
 		#address-cells = <2>;
 		#size-cells = <2>;
 
+		cpm_ap_ns_mba: mailbox-array@5240000 {
+			compatible = "google,lga-mailbox-array";
+			reg = <0x0 0x05240000 0x0 0x00010000>,
+			      <0x0 0x05250000 0x0 0x00010000>;
+			ranges = <0x0 0x0 0x05260000 0x00020000>;
+
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			cpm_ap_ns_req_mba: mailbox@0 {
+				reg = <0x00000 0x1000>;
+				interrupts = <GIC_SPI 250 IRQ_TYPE_LEVEL_HIGH 0>;
+
+				#mbox-cells = <0>;
+
+				google,tx-payload-words = <4>;
+				google,mba-queue-mode;
+			};
+
+			cpm_ap_ns_resp_mba: mailbox@1000 {
+				reg = <0x01000 0x1000>;
+				interrupts = <GIC_SPI 251 IRQ_TYPE_LEVEL_HIGH 0>;
+
+				#mbox-cells = <0>;
+
+				google,rx-payload-words = <4>;
+				google,mba-queue-mode;
+			};
+		};
+
 		gic: interrupt-controller@5880000 {
 			compatible = "arm,gic-v3";
 			reg = <0x0 0x05880000 0x0 0x10000>,
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
  2026-10-26 21:43 ` [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 13:19   ` Krzysztof Kozlowski
  2026-10-28 13:19   ` Krzysztof Kozlowski
  2026-10-26 21:43 ` [PATCH 3/9] soc: google: Add CPM over MailBox Array driver Brian Norris
                   ` (7 subsequent siblings)
  9 siblings, 2 replies; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Central Power Manager (CPM) is a co-processor on the Google Tensor
SoCs, as used on Pixel phones. CPM supports power-management and a
number of other features.

The Application Processor communicates with CPM over a pair of mailbox
channels. CPM provides clock, power-domain, reset, and interrupt
services.

Notably, CPM manages other services that are not yet included in this
binding -- some attached PMICs, an RTC, and more. For simplicity, we
don't try to describe and support all features all at once.

Because it may be useful both for reviewers familiar with the downstream
Linux support for these devices, or for future developers looking at
adapting/improving/adding further features later on, it's worth noting
some major deviations from downstream bindings:

 * CPM does not sit on the soc bus, and does not have any IO/reg region
   of its own. The downstream bindings included some not-quite-related
   hardware resources in the same "device", to aid doing some similar
   things in the same driver. For instance, some power domains
   (specifically, the GPU) are not managed by CPM, and instead are left
   for direct-register-access. Such a domain should be described
   separately, and not within the CPM node.

 * Downstream bindings tended to describe every CPM-using service as its
   own node, with a (fake) mailbox-phandle back to the CPM node. We try
   as much as possible here to avoid separate nodes if we can help it.
   And see the next point:

 * Downstream bindings tended to describe every individual clock, reset,
   and power-domain as their own sub-node, with various
   software-describing properties as part of each node. These properties
   are inherent to the SoC, however, so we avoid sub-nodes and instead
   just provide appropriate #{clock,power-domain,reset}-cells, and leave
   the per-{power-domain,clock} properties to the driver.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 .../bindings/soc/google/google,lga-cpm.yaml   |  74 +++++++
 include/dt-bindings/clock/google,lga-cpm.h    | 191 ++++++++++++++++++
 .../interrupt-controller/irq-cpm-google.h     |  16 ++
 include/dt-bindings/power/google,lga-cpm.h    |  79 ++++++++
 .../dt-bindings/reset/google,lga-cpm-reset.h  | 175 ++++++++++++++++
 include/dt-bindings/soc/google,lga-lpcm.h     |  54 +++++
 6 files changed, 589 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml
 create mode 100644 include/dt-bindings/clock/google,lga-cpm.h
 create mode 100644 include/dt-bindings/interrupt-controller/irq-cpm-google.h
 create mode 100644 include/dt-bindings/power/google,lga-cpm.h
 create mode 100644 include/dt-bindings/reset/google,lga-cpm-reset.h
 create mode 100644 include/dt-bindings/soc/google,lga-lpcm.h

diff --git a/Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml b/Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml
new file mode 100644
index 000000000000..546e6c7fff80
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/google/google,lga-cpm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Google Central Power Manager (CPM)
+
+maintainers:
+  - Brian Norris <briannorris@chromium.org>
+
+description: |
+  The Central Power Manager (CPM) is an on-chip power management subsystem
+  found on Google Tensor SoCs. It has a variety of responsibilities, from DVFS
+  and power management to resets, PMIC control, and even RTC management. The
+  Application Processor communicates with CPM over a set of mailbox channels.
+
+properties:
+  compatible:
+    const: google,lga-cpm
+
+  mboxes:
+    items:
+      - description: TX mailbox channel for sending requests to CPM
+      - description: RX mailbox channel for receiving responses from CPM
+
+  '#power-domain-cells':
+    const: 1
+    description: See <dt-bindings/power/google,lga-cpm.h> for power domain IDs.
+
+  '#reset-cells':
+    const: 2
+    description:
+      Each reset is specified by an LPCM ID (cell 1) and a reset ID (cell 2).
+      See <dt-bindings/soc/google,lga-lpcm.h> for LPCM IDs and
+      <dt-bindings/reset/google,lga-cpm-reset.h> for reset IDs.
+
+  interrupt-controller: true
+
+  '#interrupt-cells':
+    const: 1
+    description:
+      Interrupt identifiers are assigned by CPM firmware. See
+      <dt-bindings/interrupt-controller/irq-cpm-google.h> for interrupt IDs.
+
+  '#clock-cells':
+    const: 2
+    description:
+      Each clock is specified by an LPCM ID (cell 1) and a clock ID (cell
+      2). See <dt-bindings/soc/google,lga-lpcm.h> for LPCM IDs and
+      <dt-bindings/clock/google,lga-cpm.h> for clock IDs.
+
+required:
+  - compatible
+  - mboxes
+  - '#clock-cells'
+  - '#power-domain-cells'
+  - '#reset-cells'
+  - '#interrupt-cells'
+  - interrupt-controller
+
+additionalProperties: false
+
+examples:
+  - |
+    cpm: cpm {
+        compatible = "google,lga-cpm";
+        mboxes = <&cpm_ap_ns_req_mba>, <&cpm_ap_ns_resp_mba>;
+        #clock-cells = <2>;
+        #power-domain-cells = <1>;
+        #reset-cells = <2>;
+        #interrupt-cells = <1>;
+        interrupt-controller;
+    };
diff --git a/include/dt-bindings/clock/google,lga-cpm.h b/include/dt-bindings/clock/google,lga-cpm.h
new file mode 100644
index 000000000000..6d5a5567226d
--- /dev/null
+++ b/include/dt-bindings/clock/google,lga-cpm.h
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef __DT_BINDINGS_CLOCK_GOOGLE_LGA_CPM_H
+#define __DT_BINDINGS_CLOCK_GOOGLE_LGA_CPM_H
+
+/*
+ * LPCM clock ID's of each SSWRPs
+ */
+
+#define LSIOS_GPIO_CLK			0
+#define LSIOS_MCLK0_CLK			1
+#define LSIOS_PWM_CLK			2
+#define LSIOS_PWM_CFG_CLK		3
+#define LSIOS_CLI0_CFG_CLK		4
+#define LSIOS_CLI1_CFG_CLK		5
+#define LSIOS_CLI2_CFG_CLK		6
+#define LSIOS_CLI3_CFG_CLK		7
+#define LSIOS_CLI4_CFG_CLK		8
+#define LSIOS_CLI5_CFG_CLK		9
+#define LSIOS_CLI6_CFG_CLK		10
+#define LSIOS_CLI0_SPI_CLK		11
+#define LSIOS_CLI1_SPI_CLK		12
+#define LSIOS_CLI2_SPI_CLK		13
+#define LSIOS_CLI3_SPI_CLK		14
+#define LSIOS_CLI4_SPI_CLK		15
+#define LSIOS_CLI5_SPI_CLK		16
+#define LSIOS_CLI6_SPI_CLK		17
+#define LSIOS_CLI0_I2C_CLK		18
+#define LSIOS_CLI1_I2C_CLK		19
+#define LSIOS_CLI2_I2C_CLK		20
+#define LSIOS_CLI3_I2C_CLK		21
+#define LSIOS_CLI4_I2C_CLK		22
+#define LSIOS_CLI5_I2C_CLK		23
+#define LSIOS_CLI6_I2C_CLK		24
+#define LSIOS_CLI0_UART_CLK		25
+#define LSIOS_CLI1_UART_CLK		26
+#define LSIOS_CLI2_UART_CLK		27
+#define LSIOS_CLI3_UART_CLK		28
+#define LSIOS_CLI4_UART_CLK		29
+#define LSIOS_CLI5_UART_CLK		30
+#define LSIOS_CLI6_UART_CLK		31
+#define LSIOS_CLI0_I3C_CLK		32
+#define LSIOS_CLI1_I3C_CLK		33
+#define LSIOS_CLI2_I3C_CLK		34
+#define LSIOS_CLI3_I3C_CLK		35
+#define LSIOS_CLI4_I3C_CLK		36
+#define LSIOS_CLI5_I3C_CLK		37
+#define LSIOS_CLI6_I3C_CLK		38
+#define LSIOS_MCLK1_CLK			39
+#define LSIOS_MCLK2_CLK			40
+#define LSIOS_MCLK3_CLK			41
+#define LSIOS_MCLK4_CLK			42
+#define LSIOS_MCLK5_CLK			43
+#define LSIOS_MCLK6_CLK			44
+#define LSIOS_MCLK7_CLK			45
+
+#define LSION_GPIO_CLK			0
+#define LSION_CLI_INT0_I2C_CLK		1
+#define LSION_CLI_INT0_I3C_CLK		2
+#define LSION_CLI_INT0_UART_CLK		3
+#define LSION_CLI_INT0_SPI_CLK		4
+#define LSION_CLI_INT0_CFG_CLK		5
+#define LSION_CLI_INT1_I2C_CLK		6
+#define LSION_CLI_INT1_I3C_CLK		7
+#define LSION_CLI_INT1_UART_CLK		8
+#define LSION_CLI_INT1_SPI_CLK		9
+#define LSION_CLI_INT1_CFG_CLK		10
+#define LSION_CLI_INT2_I2C_CLK		11
+#define LSION_CLI_INT2_I3C_CLK		12
+#define LSION_CLI_INT2_UART_CLK		13
+#define LSION_CLI_INT2_SPI_CLK		14
+#define LSION_CLI_INT2_CFG_CLK		15
+#define LSION_CLI_INT3_I2C_CLK		16
+#define LSION_CLI_INT3_I3C_CLK		17
+#define LSION_CLI_INT3_UART_CLK		18
+#define LSION_CLI_INT3_SPI_CLK		19
+#define LSION_CLI_INT3_CFG_CLK		20
+#define LSION_CLI_INT4_I2C_CLK		21
+#define LSION_CLI_INT4_I3C_CLK		22
+#define LSION_CLI_INT4_UART_CLK		23
+#define LSION_CLI_INT4_SPI_CLK		24
+#define LSION_CLI_INT4_CFG_CLK		25
+#define LSION_CLI15_CFG_CLK		26
+#define LSION_CLI16_CFG_CLK		27
+#define LSION_CLI15_I2C_CLK		28
+#define LSION_CLI16_I2C_CLK		29
+#define LSION_CLI15_SPI_CLK		30
+#define LSION_CLI16_SPI_CLK		31
+#define LSION_CLI15_UART_CLK		32
+#define LSION_CLI16_UART_CLK		33
+#define LSION_CLI15_I3C_CLK		34
+#define LSION_CLI16_I3C_CLK		35
+
+#define LSIOE_GPIO_CLK			0
+#define LSIOE_CLI7_CFG_CLK		1
+#define LSIOE_CLI8_CFG_CLK		2
+#define LSIOE_CLI9_CFG_CLK		3
+#define LSIOE_CLI10_CFG_CLK		4
+#define LSIOE_CLI11_CFG_CLK		5
+#define LSIOE_CLI12_CFG_CLK		6
+#define LSIOE_CLI13_CFG_CLK		7
+#define LSIOE_CLI14_CFG_CLK		8
+#define LSIOE_CLI7_SPI_CLK		9
+#define LSIOE_CLI8_SPI_CLK		10
+#define LSIOE_CLI9_SPI_CLK		11
+#define LSIOE_CLI10_SPI_CLK		12
+#define LSIOE_CLI11_SPI_CLK		13
+#define LSIOE_CLI12_SPI_CLK		14
+#define LSIOE_CLI13_SPI_CLK		15
+#define LSIOE_CLI14_SPI_CLK		16
+#define LSIOE_CLI7_I2C_CLK		17
+#define LSIOE_CLI8_I2C_CLK		18
+#define LSIOE_CLI9_I2C_CLK		19
+#define LSIOE_CLI10_I2C_CLK		20
+#define LSIOE_CLI11_I2C_CLK		21
+#define LSIOE_CLI12_I2C_CLK		22
+#define LSIOE_CLI13_I2C_CLK		23
+#define LSIOE_CLI14_I2C_CLK		24
+#define LSIOE_CLI7_UART_CLK		25
+#define LSIOE_CLI8_UART_CLK		26
+#define LSIOE_CLI9_UART_CLK		27
+#define LSIOE_CLI10_UART_CLK		28
+#define LSIOE_CLI11_UART_CLK		29
+#define LSIOE_CLI12_UART_CLK		30
+#define LSIOE_CLI13_UART_CLK		31
+#define LSIOE_CLI14_UART_CLK		32
+#define LSIOE_SPMI_CLK			33
+#define LSIOE_CLI7_I3C_CLK		34
+#define LSIOE_CLI8_I3C_CLK		35
+#define LSIOE_CLI9_I3C_CLK		36
+#define LSIOE_CLI10_I3C_CLK		37
+#define LSIOE_CLI11_I3C_CLK		38
+#define LSIOE_CLI12_I3C_CLK		39
+#define LSIOE_CLI13_I3C_CLK		40
+#define LSIOE_CLI14_I3C_CLK		41
+
+#define HSIOS_MPHY_REF_OSC_CLK		0
+#define HSIOS_MPHY_REF_TCX0_CLK		1
+#define HSIOS_GPIO_REF_OSC_CLK		2
+#define HSIOS_GPIO_REF_TCX0_CLK		3
+#define HSIOS_HCLK_CLK			4
+#define HSIOS_SYS_CLK			5
+#define HSIOS_T_CLK			6
+#define HSIOS_LP_T_CLK			7
+#define HSIOS_MPHY_CFG_CLK		8
+#define HSIOS_UFS_CFG_CLK		9
+#define HSIOS_PHY_FW_CLK		10
+#define HSIOS_PCIE_REF_CLK		11
+#define HSIOS_UFS_RESET_CLK		12
+#define HSIOS_UFS_LP_RESET_CLK		13
+#define HSIOS_UFS_ARESET_CLK		14
+#define HSIOS_UFS_PHY_RESET_CLK		15
+#define HSIOS_AUX_CLK			16
+
+#define HSION_USB_CLK			0
+#define HSION_DP0_CLK			1
+#define HSION_DP1_CLK			2
+#define HSION_PHY_FW_CLK		3
+#define HSION_PCIE_REF_CLK		4
+#define HSION_U2PHY_REF_CLK		5
+#define HSION_USBDP_SUSPEND_CLK		6
+#define HSION_USBC_NON_STICKY_CLK	7
+#define HSION_USBC_STICKY_CLK		8
+#define HSION_USB_DRD_BUS_CLK		9
+#define HSION_CSR_CLK			10
+#define HSION_USB_TOP_CSR_CLK		11
+#define HSION_DP_PCLK_CLK		12
+#define HSION_DP_CFG_CLK		13
+#define HSION_U2PHY_APB_CLK		14
+#define HSION_DP_VCC_CLK		15
+#define HSION_U3PHY_REF_CLK		16
+#define HSION_USB2_PHY_RESET_CLK	17
+#define HSION_USB3DP_PHY_RESET_CLK	18
+#define HSION_EBU_CLK			19
+
+#define DPU_DP0_PIX_CLK			0
+#define DPU_DP1_PIX_CLK			1
+#define DPU_DSI0_PIX_CLK		2
+#define DPU_DSI1_PIX_CLK		3
+#define DPU_DSI1_PHY_CLK		4
+#define DPU_FE0_CLK			5
+#define DPU_FE1_CLK			6
+#define DPU_BE_CLK			7
+#define DPU_AON_CLK			8
+
+#define GPCM_DPU_PLL_DP_CLK		0
+#define GPCM_HSION_PLL_DP_CLK		1
+
+#define CPU_SSWRP_TSS_CLK		1
+
+#endif /* __DT_BINDINGS_CLOCK_GOOGLE_LGA_CPM_H */
diff --git a/include/dt-bindings/interrupt-controller/irq-cpm-google.h b/include/dt-bindings/interrupt-controller/irq-cpm-google.h
new file mode 100644
index 000000000000..26d83bfe39af
--- /dev/null
+++ b/include/dt-bindings/interrupt-controller/irq-cpm-google.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * This header provides constants for CPM IRQ.
+ *
+ * Copyright 2025-2026 Google LLC
+ *
+ */
+#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_CPM_H
+#define _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_CPM_H
+
+#define CPM_IRQ_RTC_ALARM_NO			0
+#define CPM_IRQ_AOC_MIN				7
+#define CPM_IRQ_AOC_MAX				156
+#define CPM_IRQ_AP_GDMC_WAKE			157
+
+#endif  /* _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_CPM_H */
diff --git a/include/dt-bindings/power/google,lga-cpm.h b/include/dt-bindings/power/google,lga-cpm.h
new file mode 100644
index 000000000000..4b35c59df2cc
--- /dev/null
+++ b/include/dt-bindings/power/google,lga-cpm.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef __DT_BINDINGS_POWER_GOOGLE_LGA_CPM_H
+#define __DT_BINDINGS_POWER_GOOGLE_LGA_CPM_H
+
+/* AOC */
+#define LGA_PD_SSWRP_AOC		0
+#define LGA_PD_AOC_CORE			1
+
+/* AURORA */
+#define LGA_PD_SSWRP_AURORA		2
+
+/* CODEC_3P */
+#define LGA_PD_SSWRP_CODEC_3P		3
+
+/* DPU */
+#define LGA_PD_DPU_DP			4
+#define LGA_PD_SSWRP_DPU		5
+#define LGA_PD_DPU_FE0			6
+#define LGA_PD_DPU_FE1			7
+#define LGA_PD_DPU_DSI0			8
+#define LGA_PD_DPU_DSI1			9
+
+/* EH */
+#define LGA_PD_SSWRP_EH			10
+
+/* G2D */
+#define LGA_PD_SSWRP_G2D		11
+#define LGA_PD_G2D_CORE			12
+
+/* GPDMA */
+#define LGA_PD_SSWRP_GPDMA		13
+
+/* GPU */
+#define LGA_PD_SSWRP_GPU		14
+
+/* GSW */
+#define LGA_PD_SSWRP_GSW		15
+
+/* HSIO_N */
+#define LGA_PD_SSWRP_HSIO_N		16
+#define LGA_PD_HSIO_N_USB		17
+#define LGA_PD_HSIO_N_USB_PSW		18
+#define LGA_PD_HSIO_N_PCIE		19
+#define LGA_PD_HSIO_N_DP		20
+#define LGA_PD_HSIO_N_EBU		21
+#define LGA_PD_HSIO_N_EBU_CORE		22
+
+/* HSIO_S */
+#define LGA_PD_SSWRP_HSIO_S		23
+#define LGA_PD_HSIO_S_UFS_PREP		24
+#define LGA_PD_HSIO_S_UFS		25
+#define LGA_PD_HSIO_S_PCIE		26
+#define LGA_PD_HSIO_S_SD		27
+
+/* ISPBE */
+#define LGA_PD_SSWRP_ISPBE		28
+
+/* ISPFE */
+#define LGA_PD_SSWRP_ISPFE		29
+#define LGA_PD_ISPFE_PIPE		30
+#define LGA_PD_ISPFE_CSIS		31
+
+/* LSIO_E */
+#define LGA_PD_SSWRP_LSIO_E		32
+#define LGA_PD_LSIO_E_CLI_GPIO		33
+
+/* LSIO_N */
+#define LGA_PD_SSWRP_LSIO_N		34
+#define LGA_PD_LSIO_N_CLI_GPIO		35
+
+/* LSIO_S */
+#define LGA_PD_SSWRP_LSIO_S		36
+#define LGA_PD_LSIO_S_CLI_GPIO		37
+
+/* TPU */
+#define LGA_PD_SSWRP_TPU		38
+
+#endif /* __DT_BINDINGS_POWER_GOOGLE_LGA_CPM_H */
diff --git a/include/dt-bindings/reset/google,lga-cpm-reset.h b/include/dt-bindings/reset/google,lga-cpm-reset.h
new file mode 100644
index 000000000000..d147296dbffc
--- /dev/null
+++ b/include/dt-bindings/reset/google,lga-cpm-reset.h
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef __DT_BINDINGS_RESET_GOOGLE_LGA_CPM_RESET_H
+#define __DT_BINDINGS_RESET_GOOGLE_LGA_CPM_RESET_H
+
+/* HSION */
+#define HSION_PCIE_PERST_RST			0
+#define HSION_PCIE_APP_HOLD_PHY_RST		1
+#define HSION_PCIE_INIT_RST			2
+#define HSION_PCIE_PWR_UP_RST			3
+#define HSION_USB_DP_CFG_RST			4
+#define HSION_DP_PCLK_RST			5
+#define HSION_USB_TOP_CSR_RST			6
+#define HSION_PIXEL_CK0_RST			7
+#define HSION_PIXEL_CK1_RST			8
+#define HSION_U2PHY_APB_RST			9
+#define HSION_USBC_NON_STICKY_RST		10
+#define HSION_USBC_STICKY_RST			11
+#define HSION_USB_DRD_BUS_RST			12
+#define HSION_USB2_PHY_RESET			13
+#define HSION_USB3DP_PHY_RESET			14
+#define HSION_EBU_RST				15
+
+/* HSIOS */
+#define HSIOS_PCIE_PERST_RST			0
+#define HSIOS_PCIE_APP_HOLD_PHY_RST		1
+#define HSIOS_PCIE_PWR_UP_RST			2
+#define HSIOS_UFS_PHY_RST			3
+#define HSIOS_PCIE_INIT_RST			4
+
+/* LSIOS */
+#define LSIOS_CLI0_I2C_IC_CFG_RST		0
+#define LSIOS_CLI0_I3C_IC_CFG_RST		1
+#define LSIOS_CLI0_UART_IC_CFG_RST		2
+#define LSIOS_CLI0_SPI_IC_CFG_RST		3
+#define LSIOS_CLI1_I2C_IC_CFG_RST		4
+#define LSIOS_CLI1_I3C_IC_CFG_RST		5
+#define LSIOS_CLI1_UART_IC_CFG_RST		6
+#define LSIOS_CLI1_SPI_IC_CFG_RST		7
+#define LSIOS_CLI2_I2C_IC_CFG_RST		8
+#define LSIOS_CLI2_I3C_IC_CFG_RST		9
+#define LSIOS_CLI2_UART_IC_CFG_RST		10
+#define LSIOS_CLI2_SPI_IC_CFG_RST		11
+#define LSIOS_CLI3_I2C_IC_CFG_RST		12
+#define LSIOS_CLI3_I3C_IC_CFG_RST		13
+#define LSIOS_CLI3_UART_IC_CFG_RST		14
+#define LSIOS_CLI3_SPI_IC_CFG_RST		15
+#define LSIOS_CLI4_I2C_IC_CFG_RST		16
+#define LSIOS_CLI4_I3C_IC_CFG_RST		17
+#define LSIOS_CLI4_UART_IC_CFG_RST		18
+#define LSIOS_CLI4_SPI_IC_CFG_RST		19
+#define LSIOS_CLI5_I2C_IC_CFG_RST		20
+#define LSIOS_CLI5_I3C_IC_CFG_RST		21
+#define LSIOS_CLI5_UART_IC_CFG_RST		22
+#define LSIOS_CLI5_SPI_IC_CFG_RST		23
+#define LSIOS_CLI6_I2C_IC_CFG_RST		24
+#define LSIOS_CLI6_I3C_IC_CFG_RST		25
+#define LSIOS_CLI6_UART_IC_CFG_RST		26
+#define LSIOS_CLI6_SPI_IC_CFG_RST		27
+#define LSIOS_CLI0_SPI_IC_RST			28
+#define LSIOS_CLI1_SPI_IC_RST			29
+#define LSIOS_CLI2_SPI_IC_RST			30
+#define LSIOS_CLI3_SPI_IC_RST			31
+#define LSIOS_CLI4_SPI_IC_RST			32
+#define LSIOS_CLI5_SPI_IC_RST			33
+#define LSIOS_CLI6_SPI_IC_RST			34
+#define LSIOS_CLI0_SPI_CFG_RST			35
+#define LSIOS_CLI1_SPI_CFG_RST			36
+#define LSIOS_CLI2_SPI_CFG_RST			37
+#define LSIOS_CLI3_SPI_CFG_RST			38
+#define LSIOS_CLI4_SPI_CFG_RST			39
+#define LSIOS_CLI5_SPI_CFG_RST			40
+#define LSIOS_CLI6_SPI_CFG_RST			41
+#define LSIOS_PWM_IC_RST			42
+#define LSIOS_PWM_CFG_RST			43
+#define LSIOS_GPIO_IC_RST			44
+
+/* LSION */
+#define LSION_CLI15_I2C_IC_CFG_RST		0
+#define LSION_CLI15_I3C_IC_CFG_RST		1
+#define LSION_CLI15_UART_IC_CFG_RST		2
+#define LSION_CLI15_SPI_IC_CFG_RST		3
+#define LSION_CLI16_I2C_IC_CFG_RST		4
+#define LSION_CLI16_I3C_IC_CFG_RST		5
+#define LSION_CLI16_UART_IC_CFG_RST		6
+#define LSION_CLI16_SPI_IC_CFG_RST		7
+#define LSION_CLI17_INT0_I2C_IC_CFG_RST		8
+#define LSION_CLI17_INT0_I3C_IC_CFG_RST		9
+#define LSION_CLI17_INT0_UART_IC_CFG_RST	10
+#define LSION_CLI17_INT0_SPI_IC_CFG_RST		11
+#define LSION_CLI18_INT1_I2C_IC_CFG_RST		12
+#define LSION_CLI18_INT1_I3C_IC_CFG_RST		13
+#define LSION_CLI18_INT1_UART_IC_CFG_RST	14
+#define LSION_CLI18_INT1_SPI_IC_CFG_RST		15
+#define LSION_CLI19_INT2_I2C_IC_CFG_RST		16
+#define LSION_CLI19_INT2_I3C_IC_CFG_RST		17
+#define LSION_CLI19_INT2_UART_IC_CFG_RST	18
+#define LSION_CLI19_INT2_SPI_IC_CFG_RST		19
+#define LSION_CLI20_INT3_I2C_IC_CFG_RST		20
+#define LSION_CLI20_INT3_I3C_IC_CFG_RST		21
+#define LSION_CLI20_INT3_UART_IC_CFG_RST	22
+#define LSION_CLI20_INT3_SPI_IC_CFG_RST		23
+#define LSION_CLI21_INT4_I2C_IC_CFG_RST		24
+#define LSION_CLI21_INT4_I3C_IC_CFG_RST		25
+#define LSION_CLI21_INT4_UART_IC_CFG_RST	26
+#define LSION_CLI21_INT4_SPI_IC_CFG_RST		27
+#define LSION_CLI15_SPI_IC_RST			28
+#define LSION_CLI16_SPI_IC_RST			29
+#define LSION_CLI17_INT0_SPI_IC_RST		30
+#define LSION_CLI18_INT1_SPI_IC_RST		31
+#define LSION_CLI19_INT2_SPI_IC_RST		32
+#define LSION_CLI20_INT3_SPI_IC_RST		33
+#define LSION_CLI21_INT4_SPI_IC_RST		34
+#define LSION_CLI15_SPI_CFG_RST			35
+#define LSION_CLI16_SPI_CFG_RST			36
+#define LSION_CLI17_INT0_SPI_CFG_RST		37
+#define LSION_CLI18_INT1_SPI_CFG_RST		38
+#define LSION_CLI19_INT2_SPI_CFG_RST		39
+#define LSION_CLI20_INT3_SPI_CFG_RST		40
+#define LSION_CLI21_INT4_SPI_CFG_RST		41
+#define LSION_GPIO_IC_RST			42
+
+/* LSIOE */
+#define LSIOE_CLI7_I2C_IC_CFG_RST		0
+#define LSIOE_CLI7_I3C_IC_CFG_RST		1
+#define LSIOE_CLI7_UART_IC_CFG_RST		2
+#define LSIOE_CLI7_SPI_IC_CFG_RST		3
+#define LSIOE_CLI8_I2C_IC_CFG_RST		4
+#define LSIOE_CLI8_I3C_IC_CFG_RST		5
+#define LSIOE_CLI8_UART_IC_CFG_RST		6
+#define LSIOE_CLI8_SPI_IC_CFG_RST		7
+#define LSIOE_CLI9_I2C_IC_CFG_RST		8
+#define LSIOE_CLI9_I3C_IC_CFG_RST		9
+#define LSIOE_CLI9_UART_IC_CFG_RST		10
+#define LSIOE_CLI9_SPI_IC_CFG_RST		11
+#define LSIOE_CLI10_I2C_IC_CFG_RST		12
+#define LSIOE_CLI10_I3C_IC_CFG_RST		13
+#define LSIOE_CLI10_UART_IC_CFG_RST		14
+#define LSIOE_CLI10_SPI_IC_CFG_RST		15
+#define LSIOE_CLI11_I2C_IC_CFG_RST		16
+#define LSIOE_CLI11_I3C_IC_CFG_RST		17
+#define LSIOE_CLI11_UART_IC_CFG_RST		18
+#define LSIOE_CLI11_SPI_IC_CFG_RST		19
+#define LSIOE_CLI12_I2C_IC_CFG_RST		20
+#define LSIOE_CLI12_I3C_IC_CFG_RST		21
+#define LSIOE_CLI12_UART_IC_CFG_RST		22
+#define LSIOE_CLI12_SPI_IC_CFG_RST		23
+#define LSIOE_CLI13_I2C_IC_CFG_RST		24
+#define LSIOE_CLI13_I3C_IC_CFG_RST		25
+#define LSIOE_CLI13_UART_IC_CFG_RST		26
+#define LSIOE_CLI13_SPI_IC_CFG_RST		27
+#define LSIOE_CLI14_I2C_IC_CFG_RST		28
+#define LSIOE_CLI14_I3C_IC_CFG_RST		29
+#define LSIOE_CLI14_UART_IC_CFG_RST		30
+#define LSIOE_CLI14_SPI_IC_CFG_RST		31
+#define LSIOE_CLI7_SPI_IC_RST			32
+#define LSIOE_CLI8_SPI_IC_RST			33
+#define LSIOE_CLI9_SPI_IC_RST			34
+#define LSIOE_CLI10_SPI_IC_RST			35
+#define LSIOE_CLI11_SPI_IC_RST			36
+#define LSIOE_CLI12_SPI_IC_RST			37
+#define LSIOE_CLI13_SPI_IC_RST			38
+#define LSIOE_CLI14_SPI_IC_RST			39
+#define LSIOE_CLI7_SPI_CFG_RST			40
+#define LSIOE_CLI8_SPI_CFG_RST			41
+#define LSIOE_CLI9_SPI_CFG_RST			42
+#define LSIOE_CLI10_SPI_CFG_RST			43
+#define LSIOE_CLI11_SPI_CFG_RST			44
+#define LSIOE_CLI12_SPI_CFG_RST			45
+#define LSIOE_CLI13_SPI_CFG_RST			46
+#define LSIOE_CLI14_SPI_CFG_RST			47
+#define LSIOE_GPIO_IC_RST			48
+#define LSIOE_SPMI_RST				49
+
+#endif /* __DT_BINDINGS_RESET_GOOGLE_LGA_CPM_RESET_H */
diff --git a/include/dt-bindings/soc/google,lga-lpcm.h b/include/dt-bindings/soc/google,lga-lpcm.h
new file mode 100644
index 000000000000..d1944afff2b9
--- /dev/null
+++ b/include/dt-bindings/soc/google,lga-lpcm.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef __DT_BINDINGS_SOC_GOOGLE_LGA_LPCM_H
+#define __DT_BINDINGS_SOC_GOOGLE_LGA_LPCM_H
+
+/*
+ * LPCM IDs of each SSWRP.
+ */
+#define LGA_LPCM_CPM			0
+#define LGA_LPCM_AOC			1
+#define LGA_LPCM_LSIO_E			2
+#define LGA_LPCM_LSIO_N			3
+#define LGA_LPCM_LSIO_S			4
+#define LGA_LPCM_HSIO_N			5
+#define LGA_LPCM_HSIO_S			6
+#define LGA_LPCM_GPCA			7
+#define LGA_LPCM_FABHBW			8
+#define LGA_LPCM_MEMSS			9
+#define LGA_LPCM_FABMED			10
+#define LGA_LPCM_FABRT			11
+#define LGA_LPCM_FABSTBY		12
+#define LGA_LPCM_FABSYSS		13
+#define LGA_LPCM_CPU			14
+#define LGA_LPCM_CPU_MAIN		15
+#define LGA_LPCM_CPU_CCM0		16
+#define LGA_LPCM_CPU_CCM1A		17
+#define LGA_LPCM_CPU_CCM1B		18
+#define LGA_LPCM_CPU_CCM2		19
+#define LGA_LPCM_EH			20
+#define LGA_LPCM_AURORA			21
+#define LGA_LPCM_CODEC_3P		22
+#define LGA_LPCM_G2D			23
+#define LGA_LPCM_GSW			24
+#define LGA_LPCM_ISPBE			25
+#define LGA_LPCM_ISPFE			26
+#define LGA_LPCM_INF_TCU		27
+#define LGA_LPCM_GPU			28
+#define LGA_LPCM_GPU_REMOTE_FLL		29
+#define LGA_LPCM_GPU_REMOTE_SENSOR	30
+#define LGA_LPCM_TPU			31
+#define LGA_LPCM_TPU_REMOTE		32
+#define LGA_LPCM_BMSM			33
+#define LGA_LPCM_CPUACC			34
+#define LGA_LPCM_DPU			35
+#define LGA_LPCM_GDMC			36
+#define LGA_LPCM_GMC0			37
+#define LGA_LPCM_GMC1			38
+#define LGA_LPCM_GMC2			39
+#define LGA_LPCM_GMC3			40
+#define LGA_LPCM_GPCM			41
+#define LGA_LPCM_GPDMA			42
+#define LGA_LPCM_GSA			43
+
+#endif /* __DT_BINDINGS_SOC_GOOGLE_LGA_LPCM_H */
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 3/9] soc: google: Add CPM over MailBox Array driver
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
  2026-10-26 21:43 ` [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array Brian Norris
  2026-10-26 21:43 ` [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 13:27   ` Krzysztof Kozlowski
  2026-10-26 21:43 ` [PATCH 4/9] pmdomain: google: Add CPM power-domain driver Brian Norris
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Central Power Manager (CPM) is a co-processor on the Google Tensor
SoCs, as used on Pixel phones. CPM supports power-management and a
number of other features.

The Application Processor communicates with CPM over a pair of mailbox
channels (the "MailBox Array" (MBA)). CPM provides clock, power-domain,
reset, interrupt services, and more.

It's difficult to support all of these services within a single driver,
in part because it's a lot of code to manage, but also because there can
be complex relationships between the individual services. For example,
clocks may have dependencies on power-domains. Instead, the CPM driver
only handles some basic initialization, and otherwise creates child
auxiliary_bus devices to handle each service. It also provides an API
wrapper around the mailbox protocol, so clients don't have to worry
about some of the details. This API assumes all users are children of
the CPM device.

Implementation note about the "session" handling: the mailbox API
doesn't provide any timeouts or guarantees about a response message, but
CPM clients would like an (optional) timeout. So there's a little bit of
complexity around reference-counting a "session" (i.e., outstanding
request still waiting on a response), as that session may outlive the
client's timeout.

Quick note about hardware queueing: this is implemented in our mailbox
driver by allowing clients to request the "same" channel multiple times.
A client is expected to keep requesting the channel until it sees
-EBUSY, meaning we've requested all of them. We then utilize those
channels in a round-robin fashion.

Because it may be useful both for reviewers familiar with the downstream
Linux support for these devices, or for future developers looking at
adapting/improving/adding further features later on, it's worth noting
some major deviations from the downstream drivers:

 * This driver doesn't contain much of the queueing complexity from the
   downstream driver. The mailbox driver was significantly rewritten
   beneath us, to support most of the queueing there, and thus exposes a
   simpler API to us.

 * No session pool; we just allocate sessions on-demand, enough to
   handle timeouts while a response is still pending.

 * Use auxiliary_bus, with CPM services represented as child devices on
   this bus. Downstream services tended to be a bunch of artificial
   platform devices, with fake-mailbox phandles to the CPM "interface"
   node. This is an atypical device tree structure, and most of those
   platform devices came from device nodes that were less for describing
   the hardware, and more for describing how we wanted software to look.
   So far, I haven't found services that don't work as child auxiliary
   devices. If we later have examples that need some combination of both
   iomem resources and CPM resources (and so may need to live on the
   platform bus), I'm sure we can add support for non-child (e.g.,
   phandle) interface methods.

 * Add devm_* client helpers, to make it easier for clients to clean up
   properly. (Hint: most downstream clients did not clean up properly.)

 * Reduce the surface area of exposed (EXPORTed, or header definitions)
   protocol details, such as goog_mba_q_*() methods. Many such details
   aren't used outside the CPM driver, so it's probably best to keep
   them local until proven otherwise.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---
Based on a WIP mailbox API from:

  https://lore.kernel.org/all/20261023165211.v2.4.I5cef580a62c86ba6c3465ad336fa509afbdf1476@changeid/
  [PATCH v2 4/4] mailbox: goog-mba: Introduce the goog-mba mailbox driver

which is part of this series:

  https://lore.kernel.org/all/20261023235408.3063350-1-dianders@chromium.org/
  [PATCH v2 0/4] mailbox: Improve the mbox core then introduce the goog-mba driver

That API consists of:

 1) a header (<linux/mailbox/goog-mba-message.h>), which so far seems
    typical and uncontroversial and

 2) a strategy for supporting hardware queueing -- where the current
    proposal is described in the commit message. I suppose this strategy
    could still change, although it was discussed at length in v1 of
    that series.

Anyway, this patch should not be accepted until the above series is
settled.

 drivers/soc/Kconfig                       |   1 +
 drivers/soc/Makefile                      |   1 +
 drivers/soc/google/Kconfig                |  13 +
 drivers/soc/google/Makefile               |   3 +
 drivers/soc/google/google-cpm-mba-trace.h | 214 +++++
 drivers/soc/google/google-cpm-mba.c       | 964 ++++++++++++++++++++++
 drivers/soc/google/google-cpm-mba.h       |  30 +
 include/soc/google/goog_cpm_service_ids.h |  84 ++
 include/soc/google/google-cpm-mba.h       | 147 ++++
 9 files changed, 1457 insertions(+)
 create mode 100644 drivers/soc/google/Kconfig
 create mode 100644 drivers/soc/google/Makefile
 create mode 100644 drivers/soc/google/google-cpm-mba-trace.h
 create mode 100644 drivers/soc/google/google-cpm-mba.c
 create mode 100644 drivers/soc/google/google-cpm-mba.h
 create mode 100644 include/soc/google/goog_cpm_service_ids.h
 create mode 100644 include/soc/google/google-cpm-mba.h

diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
index 483cbdcae153..fbe6460424c7 100644
--- a/drivers/soc/Kconfig
+++ b/drivers/soc/Kconfig
@@ -10,6 +10,7 @@ source "drivers/soc/canaan/Kconfig"
 source "drivers/soc/cirrus/Kconfig"
 source "drivers/soc/fsl/Kconfig"
 source "drivers/soc/fujitsu/Kconfig"
+source "drivers/soc/google/Kconfig"
 source "drivers/soc/hisilicon/Kconfig"
 source "drivers/soc/imx/Kconfig"
 source "drivers/soc/ixp4xx/Kconfig"
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
index 5a3efd68355c..6241e543dad7 100644
--- a/drivers/soc/Makefile
+++ b/drivers/soc/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_MACH_DOVE)		+= dove/
 obj-y				+= fsl/
 obj-y				+= fujitsu/
 obj-$(CONFIG_ARCH_GEMINI)	+= gemini/
+obj-y				+= google/
 obj-y				+= hisilicon/
 obj-y				+= imx/
 obj-y				+= ixp4xx/
diff --git a/drivers/soc/google/Kconfig b/drivers/soc/google/Kconfig
new file mode 100644
index 000000000000..9cd04be63b23
--- /dev/null
+++ b/drivers/soc/google/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config GOOGLE_CPM_MBA
+	tristate "Google Tensor CPM MailBox Array Interface"
+	depends on ARCH_GOOGLE || COMPILE_TEST
+	depends on MAILBOX
+	select AUXILIARY_BUS
+	help
+	  Driver for interfacing with the Central Power Manager (CPM) MailBox
+	  Array (MBA) found on Google Tensor SoCs. The driver provides APIs for
+	  communication between the Application Processor and the CPM, and
+	  is required by CPM client drivers such as clock, reset, power
+	  domain, and more.
diff --git a/drivers/soc/google/Makefile b/drivers/soc/google/Makefile
new file mode 100644
index 000000000000..78f69932a377
--- /dev/null
+++ b/drivers/soc/google/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-$(CONFIG_GOOGLE_CPM_MBA) += google-cpm-mba.o
diff --git a/drivers/soc/google/google-cpm-mba-trace.h b/drivers/soc/google/google-cpm-mba-trace.h
new file mode 100644
index 000000000000..a10687d0b83e
--- /dev/null
+++ b/drivers/soc/google/google-cpm-mba-trace.h
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2025-2026 Google LLC
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM google_cpm_mba
+
+#if !defined(_GOOGLE_CPM_MBA_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _GOOGLE_CPM_MBA_TRACE_H
+
+#include <linux/tracepoint.h>
+#include <soc/google/google-cpm-mba.h>
+
+#include "google-cpm-mba.h"
+
+#define MAX_DEV_NAME 32
+
+#ifdef CREATE_TRACE_POINTS
+static inline void cpm_trace_clone_payload(u32 *header, u32 *payloads,
+					   const struct cpm_payload *payload)
+{
+	*header = payload->header;
+	memcpy(payloads, payload->payload, GOOG_MBA_PAYLOAD_SIZE * sizeof(*payloads));
+}
+#endif
+
+TRACE_EVENT(
+	cpm_request_client,
+
+	TP_PROTO(struct cpm_client *client),
+
+	TP_ARGS(client),
+
+	TP_STRUCT__entry(
+		__field(int, src_id)
+		__field(void *, host_tx_cb)
+		__array(char, dev_name, MAX_DEV_NAME)
+	),
+
+	TP_fast_assign(
+		scnprintf(__entry->dev_name, MAX_DEV_NAME, "%s", dev_name(client->dev));
+		__entry->src_id = client->src_id;
+		__entry->host_tx_cb = (void *)client->host_tx_cb;
+	),
+
+	TP_printk("dev=%s src_id=%d host_tx_cb=%ps", __entry->dev_name, __entry->src_id,
+		  __entry->host_tx_cb)
+);
+
+TRACE_EVENT(
+	cpm_free_client,
+
+	TP_PROTO(struct cpm_client *client),
+
+	TP_ARGS(client),
+
+	TP_STRUCT__entry(
+		__field(int, src_id)
+		__array(char, dev_name, MAX_DEV_NAME)
+	),
+
+	TP_fast_assign(
+		__entry->src_id = client->src_id;
+		scnprintf(__entry->dev_name, MAX_DEV_NAME, "%s", dev_name(client->dev));
+	),
+
+	TP_printk("dev=%s src_id=%d", __entry->dev_name, __entry->src_id)
+);
+
+TRACE_EVENT(
+	cpm_req_enqueue,
+
+	TP_PROTO(struct cpm_session *session, const struct cpm_request *req, int src_id),
+
+	TP_ARGS(session, req, src_id),
+
+	TP_STRUCT__entry(
+		__field(void *, session)
+		__field(unsigned long, tout_ms)
+		__field(u32, resp_context)
+		__field(int, src_id)
+	),
+
+	TP_fast_assign(
+		__entry->tout_ms = req->tout_ms;
+		__entry->resp_context = req->resp_context;
+		__entry->session = session;
+		__entry->src_id = src_id;
+	),
+
+	TP_printk("session=%p src_id=%d tout_ms=%lu resp_ctx=%#x",
+		  __entry->session, __entry->src_id, __entry->tout_ms,
+		  __entry->resp_context)
+);
+
+TRACE_EVENT(
+	cpm_submit_msg,
+
+	TP_PROTO(struct cpm_session *session, const struct cpm_payload *payload),
+
+	TP_ARGS(session, payload),
+
+	TP_STRUCT__entry(
+		__field(void *, session)
+		__field(u32, header)
+		__array(u32, payloads, GOOG_MBA_PAYLOAD_SIZE)
+	),
+
+	TP_fast_assign(
+		__entry->session = session;
+		cpm_trace_clone_payload(&__entry->header, __entry->payloads, payload);
+	),
+
+	TP_printk("session=%p: %#010x %#010x %#010x %#010x",
+		  __entry->session, __entry->header,
+		  __entry->payloads[0], __entry->payloads[1], __entry->payloads[2])
+);
+
+TRACE_EVENT(
+	cpm_req_tx_done,
+
+	TP_PROTO(struct cpm_session *session),
+
+	TP_ARGS(session),
+
+	TP_STRUCT__entry(
+		__field(void *, session)
+	),
+
+	TP_fast_assign(
+		__entry->session = session;
+	),
+
+	TP_printk("session=%p", __entry->session)
+);
+
+TRACE_EVENT(
+	cpm_resp_cb,
+
+	TP_PROTO(struct cpm_session *session, const struct cpm_payload *payload),
+
+	TP_ARGS(session, payload),
+
+	TP_STRUCT__entry(
+		__field(void *, session)
+		__field(u32, header)
+		__array(u32, payloads, GOOG_MBA_PAYLOAD_SIZE)
+	),
+
+	TP_fast_assign(
+		__entry->session = session;
+		cpm_trace_clone_payload(&__entry->header, __entry->payloads, payload);
+	),
+
+	TP_printk("session=%p: %#010x %#010x %#010x %#010x",
+		  __entry->session, __entry->header,
+		  __entry->payloads[0], __entry->payloads[1], __entry->payloads[2])
+);
+
+TRACE_EVENT(
+	cpm_send_message,
+
+	TP_PROTO(struct cpm_session *session, int ret),
+
+	TP_ARGS(session, ret),
+
+	TP_STRUCT__entry(
+		__field(void *, session)
+		__field(int, ret)
+	),
+
+	TP_fast_assign(
+		__entry->session = session;
+		__entry->ret = ret;
+	),
+
+	TP_printk("session=%p ret=%d",
+		  __entry->session, __entry->ret)
+);
+
+TRACE_EVENT(
+	cpm_handle_host_tx,
+
+	TP_PROTO(int dst_id, u32 resp_context, const struct cpm_payload *payload),
+
+	TP_ARGS(dst_id, resp_context, payload),
+
+	TP_STRUCT__entry(
+		__field(int, dst_id)
+		__field(u32, resp_context)
+		__field(u32, header)
+		__array(u32, payloads, GOOG_MBA_PAYLOAD_SIZE)
+	),
+
+	TP_fast_assign(
+		__entry->dst_id = dst_id;
+		__entry->resp_context = resp_context;
+		cpm_trace_clone_payload(&__entry->header, __entry->payloads, payload);
+	),
+
+	TP_printk("dst_id=%d resp_context=%#x: %#010x %#010x %#010x %#010x",
+		  __entry->dst_id, __entry->resp_context,
+		  __entry->header, __entry->payloads[0],
+		  __entry->payloads[1], __entry->payloads[2])
+);
+
+#endif /* _GOOGLE_CPM_MBA_TRACE_H */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../drivers/soc/google/
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE google-cpm-mba-trace
+#include <trace/define_trace.h>
diff --git a/drivers/soc/google/google-cpm-mba.c b/drivers/soc/google/google-cpm-mba.c
new file mode 100644
index 000000000000..629a071e202c
--- /dev/null
+++ b/drivers/soc/google/google-cpm-mba.c
@@ -0,0 +1,964 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Google MailBox Array (MBA) CPM Interface
+ *
+ * Copyright 2023-2026 Google LLC
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/lockdep.h>
+#include <linux/mailbox/goog-mba-message.h>
+#include <linux/mailbox_client.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#include <soc/google/google-cpm-mba.h>
+
+#include "google-cpm-mba.h"
+
+/*
+ * Header of queue mode message defines fields for type, src_id and dst_id.
+ *
+ *  31 30     29  24      20    16     8     0
+ *  +---+------+---+-------+-----+-----+-----+
+ *  | V | Type | R | Token | Seq | Src | Dst |
+ *  +---+------+---+-------+-----+-----+-----+
+ *
+ *  Bit 31 is valid bit:	Must be 1.
+ *  Bits 30-29 are type:	The message type can be one of the following values:
+ *				0x0 One-way message
+ *				0x1 Requests (expect response)
+ *				0x2 Response (to a previous request)
+ *  Bits 28-24 are reserved.
+ *  Bits 23-20 are token:	Reserved for transport layer module.
+ *  Bits 19-16 are seq:		Reserved for transport layer module.
+ *  Bits 15-8 are source id.
+ *  Bits 7-0 are destination id.
+ */
+
+#define GOOG_MBA_Q_DST_ID_MASK		GENMASK(7, 0)
+#define GOOG_MBA_Q_SRC_ID_MASK		GENMASK(15, 8)
+#define GOOG_MBA_Q_SEQ_MASK		GENMASK(19, 16)
+#define GOOG_MBA_Q_TOKEN_MASK		GENMASK(23, 20)
+#define GOOG_MBA_Q_TYPE_MASK		GENMASK(30, 29)
+#define GOOG_MBA_Q_VALID_MASK		GENMASK(31, 31)
+
+#define GOOG_MBA_Q_MAX_SRC_ID_VAL	0xff
+#define GOOG_MBA_Q_MIN_SEQ_VAL		1
+#define GOOG_MBA_Q_MAX_SEQ_VAL		13
+#define GOOG_MBA_Q_MAX_TOKEN_VAL	16
+
+#define GOOG_MBA_Q_INIT_PROTO_VAL	0x100
+
+#define GOOG_MBA_Q_TYPE_ONEWAY		0x0
+#define GOOG_MBA_Q_TYPE_REQUEST		0x1
+#define GOOG_MBA_Q_TYPE_RESPONSE	0x2
+
+static inline u32 goog_mba_q_get_dst_id(const u32 *msg)
+{
+	return FIELD_GET(GOOG_MBA_Q_DST_ID_MASK, *msg);
+}
+
+static inline u32 goog_mba_q_get_src_id(const u32 *msg)
+{
+	return FIELD_GET(GOOG_MBA_Q_SRC_ID_MASK, *msg);
+}
+
+static inline u32 goog_mba_q_get_seq(const u32 *msg)
+{
+	return FIELD_GET(GOOG_MBA_Q_SEQ_MASK, *msg);
+}
+
+static inline u32 goog_mba_q_get_token(const u32 *msg)
+{
+	return FIELD_GET(GOOG_MBA_Q_TOKEN_MASK, *msg);
+}
+
+static inline u32 goog_mba_q_get_type(const u32 *msg)
+{
+	return FIELD_GET(GOOG_MBA_Q_TYPE_MASK, *msg);
+}
+
+static inline u32 goog_mba_q_get_valid(const u32 *msg)
+{
+	return FIELD_GET(GOOG_MBA_Q_VALID_MASK, *msg);
+}
+
+static inline void goog_mba_q_set_dst_id(u32 *msg, u32 dst_id)
+{
+	*msg &= ~GOOG_MBA_Q_DST_ID_MASK;
+	*msg |= FIELD_PREP(GOOG_MBA_Q_DST_ID_MASK, dst_id);
+}
+
+static inline void goog_mba_q_set_src_id(u32 *msg, u32 src_id)
+{
+	*msg &= ~GOOG_MBA_Q_SRC_ID_MASK;
+	*msg |= FIELD_PREP(GOOG_MBA_Q_SRC_ID_MASK, src_id);
+}
+
+static inline void goog_mba_q_set_seq(u32 *msg, u32 seq)
+{
+	*msg &= ~GOOG_MBA_Q_SEQ_MASK;
+	*msg |= FIELD_PREP(GOOG_MBA_Q_SEQ_MASK, seq);
+}
+
+static inline void goog_mba_q_set_token(u32 *msg, u32 token)
+{
+	*msg &= ~GOOG_MBA_Q_TOKEN_MASK;
+	*msg |= FIELD_PREP(GOOG_MBA_Q_TOKEN_MASK, token);
+}
+
+static inline void goog_mba_q_set_type(u32 *msg, u32 type)
+{
+	*msg &= ~GOOG_MBA_Q_TYPE_MASK;
+	*msg |= FIELD_PREP(GOOG_MBA_Q_TYPE_MASK, type);
+}
+
+static inline void goog_mba_q_set_valid(u32 *msg, bool is_valid)
+{
+	u32 val = is_valid ? 1 : 0;
+	*msg &= ~GOOG_MBA_Q_VALID_MASK;
+	*msg |= FIELD_PREP(GOOG_MBA_Q_VALID_MASK, val);
+}
+
+static inline u32 goog_mba_q_create_hdr(u32 src_id, u32 dst_id, u32 seq,
+					u32 token, u32 type)
+{
+	return FIELD_PREP(GOOG_MBA_Q_SRC_ID_MASK, src_id) |
+	       FIELD_PREP(GOOG_MBA_Q_DST_ID_MASK, dst_id) |
+	       FIELD_PREP(GOOG_MBA_Q_SEQ_MASK, seq) |
+	       FIELD_PREP(GOOG_MBA_Q_TOKEN_MASK, token) |
+	       FIELD_PREP(GOOG_MBA_Q_TYPE_MASK, type) |
+	       FIELD_PREP(GOOG_MBA_Q_VALID_MASK, 1);
+}
+
+struct cpm_session {
+	struct kref refcount;
+	struct completion complete;
+	struct cpm_payload req_msg;
+	struct cpm_payload resp_msg;
+	struct goog_mba_tx_msg req_mba_msg;
+};
+
+#define CREATE_TRACE_POINTS
+#include "google-cpm-mba-trace.h"
+
+#define MBOX_IDX_REQ			0
+#define MBOX_IDX_RESP			1
+
+struct cpm_mbox_chan_info {
+	struct mbox_client client;
+	struct mbox_chan *chan;
+	struct list_head entry;
+};
+
+/**
+ * struct cpm_data - Core data for the CPM interface module
+ * @dev: The device structure associated with this CPM interface.
+ * @client_list_head: List of registered client handles.
+ * @lock: Spinlock for protecting critical sections.
+ * @seq_num: Sequence number for debugging.
+ * @resp_pending: Array for sessions awaiting responses from the CPM host.
+ * @resp_pending_size: Number of @resp_pending entries in use.
+ * @req_chans: Circular list of request mailbox channels (rotated round-robin).
+ * @resp_chan: Response mailbox channel.
+ */
+struct cpm_data {
+	struct device *dev;
+	struct list_head client_list_head;
+
+	/*
+	 * Protect queue operations including token, resp_pending, req_chans,
+	 * and resp_chan.
+	 */
+	spinlock_t lock;
+	int seq_num;
+	struct cpm_session *resp_pending[GOOG_MBA_Q_MAX_TOKEN_VAL];
+	int resp_pending_size;
+	struct list_head req_chans;
+	struct cpm_mbox_chan_info resp_chan;
+};
+
+static const struct of_device_id cpm_of_match_table[] = {
+	{ .compatible = "google,lga-cpm" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, cpm_of_match_table);
+
+static inline bool cpm_is_request_msg(const struct cpm_request *req)
+{
+	return req->msg_type == REQUEST_MSG;
+}
+
+static inline bool cpm_is_oneway_msg(const struct cpm_request *req)
+{
+	return req->msg_type == ONEWAY_MSG;
+}
+
+static inline bool cpm_is_response_msg(const struct cpm_request *req)
+{
+	return req->msg_type == RESPONSE_MSG;
+}
+
+static inline bool cpm_is_request_header(const void *msg)
+{
+	return goog_mba_q_get_type(msg) == GOOG_MBA_Q_TYPE_REQUEST;
+}
+
+static inline bool cpm_is_oneway_header(const void *msg)
+{
+	return goog_mba_q_get_type(msg) == GOOG_MBA_Q_TYPE_ONEWAY;
+}
+
+static inline bool cpm_is_response_header(const void *msg)
+{
+	return goog_mba_q_get_type(msg) == GOOG_MBA_Q_TYPE_RESPONSE;
+}
+
+static inline bool cpm_is_init_header(const void *msg)
+{
+	return *(const u32 *)msg == GOOG_MBA_Q_INIT_PROTO_VAL;
+}
+
+static void cpm_session_release(struct kref *kref)
+{
+	struct cpm_session *session = container_of(kref, struct cpm_session, refcount);
+
+	kfree(session);
+}
+
+DEFINE_FREE(session_put, struct cpm_session *, if (_T) kref_put(&_T->refcount, cpm_session_release))
+
+/**
+ * cpm_add_pending_resp - add session to pending table to wait for host response
+ * @cpm: Core driver data.
+ * @session: Client's session waiting for response.
+ *
+ * Return: 0 on success, -EBUSY if no token is available.
+ */
+static int cpm_add_pending_resp(struct cpm_data *cpm, struct cpm_session *session)
+{
+	struct device *dev = cpm->dev;
+	int token;
+
+	lockdep_assert_held(&cpm->lock);
+
+	for (token = 0; token < GOOG_MBA_Q_MAX_TOKEN_VAL; token++) {
+		if (!cpm->resp_pending[token]) {
+			goog_mba_q_set_token(&session->req_msg.header, token);
+			kref_get(&session->refcount);
+			cpm->resp_pending[token] = session;
+			cpm->resp_pending_size++;
+			return 0;
+		}
+	}
+
+	dev_err(dev, "Failed to find available entries in resp_pending (size:%d)\n",
+		cpm->resp_pending_size);
+
+	return -EBUSY;
+}
+
+/**
+ * cpm_remove_pending_resp - remove session entry from pending table after received response
+ * @cpm: Core driver data.
+ * @msg_payload: Used to resolve embedded token value to remove corresponding
+ *	entry of pending table.
+ *
+ * Return: True if a matching session was found and removed, false otherwise.
+ */
+static bool
+cpm_remove_pending_resp(struct cpm_data *cpm,
+			const struct cpm_payload *msg_payload)
+{
+	struct cpm_session *session;
+	int token = goog_mba_q_get_token(&msg_payload->header);
+
+	lockdep_assert_held(&cpm->lock);
+
+	session = cpm->resp_pending[token];
+	if (!session)
+		return false;
+
+	cpm->resp_pending[token] = NULL;
+	cpm->resp_pending_size--;
+
+	if (cpm_is_response_header(msg_payload)) {
+		trace_cpm_resp_cb(session, msg_payload);
+		session->resp_msg = *msg_payload;
+		complete(&session->complete);
+	}
+
+	kref_put(&session->refcount, cpm_session_release);
+
+	return true;
+}
+
+static void
+cpm_remove_pending_by_session(struct cpm_data *cpm,
+			      struct cpm_session *session)
+{
+	int token;
+
+	lockdep_assert_held(&cpm->lock);
+
+	for (token = 0; token < GOOG_MBA_Q_MAX_TOKEN_VAL; token++) {
+		if (cpm->resp_pending[token] == session) {
+			cpm->resp_pending[token] = NULL;
+			cpm->resp_pending_size--;
+			kref_put(&session->refcount, cpm_session_release);
+			return;
+		}
+	}
+}
+
+/**
+ * cpm_set_seq_number - assign sequential number in valid range
+ * @cpm: Core driver data.
+ * @req_msg: Request payload to write sequence number to.
+ */
+static inline void cpm_set_seq_number(struct cpm_data *cpm,
+				      struct cpm_payload *req_msg)
+{
+	int seq_num;
+
+	seq_num = cpm->seq_num;
+	goog_mba_q_set_seq(&req_msg->header, seq_num);
+	if (seq_num == GOOG_MBA_Q_MAX_SEQ_VAL)
+		cpm->seq_num = GOOG_MBA_Q_MIN_SEQ_VAL;
+	else
+		cpm->seq_num++;
+}
+
+static struct cpm_client *
+cpm_search_client_by_id(struct cpm_data *cpm, int id)
+{
+	struct cpm_client *client;
+
+	if (id < 0 || id > GOOG_MBA_Q_MAX_SRC_ID_VAL)
+		return ERR_PTR(-EINVAL);
+
+	list_for_each_entry(client, &cpm->client_list_head, entry) {
+		if (client->src_id == id)
+			return client;
+	}
+
+	return NULL;
+}
+
+struct cpm_client *cpm_request_client(struct device *dev,
+				      int src_id,
+				      cpm_cb_t cpm_cb,
+				      void *priv)
+{
+	struct cpm_client *client;
+	struct cpm_data *cpm;
+
+	if (!dev_is_auxiliary(dev)) {
+		dev_err(dev, "Device is not an auxiliary device\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (WARN_ON(!of_match_node(cpm_of_match_table, dev->parent->of_node))) {
+		dev_err(dev, "Parent device is not CPM: %s\n", dev_name(dev->parent));
+		return ERR_PTR(-EINVAL);
+	}
+
+	cpm = dev_get_drvdata(dev->parent);
+	if (WARN_ON(!cpm))
+		return ERR_PTR(-ENODEV);
+
+	client = kzalloc_obj(*client, GFP_KERNEL);
+	if (!client)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&client->entry);
+	client->dev = dev;
+	client->src_id = src_id;
+	client->host_tx_cb = cpm_cb;
+	client->priv = priv;
+
+	scoped_guard(spinlock_irqsave, &cpm->lock) {
+		if (src_id) {
+			struct cpm_client *existing;
+
+			existing = cpm_search_client_by_id(cpm, src_id);
+			if (IS_ERR(existing)) {
+				dev_err(dev, "src_id(%d) out of valid range\n", src_id);
+				kfree(client);
+				return existing;
+			}
+
+			if (existing) {
+				kfree(client);
+				return ERR_PTR(-EBUSY);
+			}
+		}
+		list_add(&client->entry, &cpm->client_list_head);
+	}
+
+	trace_cpm_request_client(client);
+
+	return client;
+}
+EXPORT_SYMBOL_GPL(cpm_request_client);
+
+void cpm_free_client(struct cpm_client *client)
+{
+	struct cpm_data *cpm = dev_get_drvdata(client->dev->parent);
+
+	trace_cpm_free_client(client);
+
+	scoped_guard(spinlock_irqsave, &cpm->lock)
+		list_del(&client->entry);
+
+	kfree(client);
+}
+EXPORT_SYMBOL_GPL(cpm_free_client);
+
+static void devm_cpm_client_release(void *data)
+{
+	struct cpm_client *client = data;
+
+	cpm_free_client(client);
+}
+
+struct cpm_client *devm_cpm_request_client(struct device *dev,
+					   int src_id,
+					   cpm_cb_t cpm_cb,
+					   void *priv)
+{
+	struct cpm_client *client;
+	int ret;
+
+	client = cpm_request_client(dev, src_id, cpm_cb, priv);
+	if (IS_ERR(client))
+		return client;
+
+	ret = devm_add_action_or_reset(dev, devm_cpm_client_release, client);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return client;
+}
+EXPORT_SYMBOL_GPL(devm_cpm_request_client);
+
+static u32 cpm_req_get_header(struct cpm_client *client, struct cpm_request *cpm_req)
+{
+	u32 header = 0;
+
+	if (cpm_is_oneway_msg(cpm_req)) {
+		header = goog_mba_q_create_hdr(client->src_id, cpm_req->dst_id,
+					       0, 0, GOOG_MBA_Q_TYPE_ONEWAY);
+	} else if (cpm_is_request_msg(cpm_req)) {
+		header = goog_mba_q_create_hdr(client->src_id, cpm_req->dst_id,
+					       0, 0, GOOG_MBA_Q_TYPE_REQUEST);
+	} else if (cpm_is_response_msg(cpm_req)) {
+		goog_mba_q_set_type(&cpm_req->resp_context,
+				    GOOG_MBA_Q_TYPE_RESPONSE);
+		header = cpm_req->resp_context;
+	}
+
+	return header;
+}
+
+static int cpm_try_submit(struct cpm_data *cpm, struct cpm_session *session)
+{
+	struct device *dev = cpm->dev;
+	struct cpm_payload *req_msg = &session->req_msg;
+	struct cpm_mbox_chan_info *mbox_chan_info;
+	int ret;
+
+	lockdep_assert_held(&cpm->lock);
+
+	if (!cpm_is_init_header(req_msg)) {
+		session->req_mba_msg.init = false;
+		if (cpm_is_request_header(req_msg)) {
+			ret = cpm_add_pending_resp(cpm, session);
+			if (ret < 0)
+				return ret;
+		}
+		cpm_set_seq_number(cpm, req_msg);
+	} else {
+		session->req_mba_msg.init = true;
+	}
+
+	mbox_chan_info = list_first_entry(&cpm->req_chans,
+					  struct cpm_mbox_chan_info, entry);
+
+	/* Get ref for request channel TX completion before submit */
+	kref_get(&session->refcount);
+
+	session->req_mba_msg.payload = (u32 *)req_msg;
+	session->req_mba_msg.payload_words =
+		sizeof(*req_msg) / sizeof(*session->req_mba_msg.payload);
+
+	ret = mbox_send_message(mbox_chan_info->chan, &session->req_mba_msg);
+	trace_cpm_submit_msg(session, req_msg);
+	if (ret < 0) {
+		dev_err(dev, "mbox_send_message failed to send (ret:%d)\n", ret);
+		dev_err(dev, "header: %#x, payload: [%#x, %#x, %#x]\n",
+			req_msg->header, req_msg->payload[0],
+			req_msg->payload[1], req_msg->payload[2]);
+		dev_err(dev, "[mbox_chan] msg_count:%u, msg_free:%u\n",
+			mbox_chan_info->chan->msg_count,
+			mbox_chan_info->chan->msg_free);
+		if (!cpm_is_init_header(req_msg) && cpm_is_request_header(req_msg))
+			cpm_remove_pending_resp(cpm, req_msg);
+		/* Put ref for request channel since submit failed */
+		kref_put(&session->refcount, cpm_session_release);
+
+		return ret;
+	}
+
+	list_rotate_left(&cpm->req_chans);
+
+	return 0;
+}
+
+static int __cpm_send_message(struct cpm_data *cpm,
+			      struct cpm_request *cpm_req,
+			      u32 header, int src_id)
+{
+	unsigned long wait = MAX_SCHEDULE_TIMEOUT;
+	int ret;
+
+	struct cpm_session *session __free(session_put) = kzalloc_obj(*session, GFP_KERNEL);
+	if (!session)
+		return -ENOMEM;
+
+	cpm_req->req_msg->header = header;
+
+	/* Initialize session */
+	kref_init(&session->refcount);
+	init_completion(&session->complete);
+
+	session->req_msg = *cpm_req->req_msg;
+
+	trace_cpm_req_enqueue(session, cpm_req, src_id);
+
+	scoped_guard(spinlock_irqsave, &cpm->lock) {
+		ret = cpm_try_submit(cpm, session);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (cpm_req->tout_ms)
+		wait = msecs_to_jiffies(cpm_req->tout_ms);
+
+	if (wait_for_completion_timeout(&session->complete, wait) == 0) {
+		ret = -ETIMEDOUT;
+		/* Clean up pending response mapping on timeout */
+		scoped_guard(spinlock_irqsave, &cpm->lock)
+			cpm_remove_pending_by_session(cpm, session);
+	} else if (cpm_is_request_msg(cpm_req) && cpm_req->resp_msg) {
+		*cpm_req->resp_msg = session->resp_msg;
+	}
+	trace_cpm_send_message(session, ret);
+
+	return ret;
+}
+
+static bool cpm_iface_is_valid_req(struct cpm_data *cpm,
+				   struct cpm_request *cpm_req)
+{
+	struct device *dev = cpm->dev;
+
+	if (!cpm_req) {
+		dev_err(dev, "cpm_req is NULL\n");
+		return false;
+	}
+
+	if (!cpm_req->req_msg) {
+		dev_err(dev, "cpm_req->req_msg is NULL\n");
+		return false;
+	}
+
+	if (cpm_req->msg_type > RESPONSE_MSG || cpm_req->msg_type < ONEWAY_MSG) {
+		dev_err(dev, "Invalid msg_type: %d\n", cpm_req->msg_type);
+		return false;
+	}
+
+	if (cpm_is_request_msg(cpm_req) && !cpm_req->resp_msg) {
+		dev_err(dev, "cpm_req->resp_msg is NULL when msg_type is REQUEST_MSG\n");
+		return false;
+	}
+
+	if (cpm_is_response_msg(cpm_req)) {
+		if (!goog_mba_q_get_valid(&cpm_req->resp_context)) {
+			dev_err(dev, "Invalid valid bit in resp_context: %#x\n",
+				cpm_req->resp_context);
+			return false;
+		}
+
+		if (!cpm_is_response_header(&cpm_req->resp_context)) {
+			dev_err(dev, "Invalid response type in resp_context: %#x\n",
+				cpm_req->resp_context);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+int cpm_send_message(struct cpm_client *client, struct cpm_request *cpm_req)
+{
+	struct cpm_data *cpm;
+	u32 header;
+
+	if (!client)
+		return -EINVAL;
+
+	cpm = dev_get_drvdata(client->dev->parent);
+	if (!cpm)
+		return -ENODEV;
+
+	if (!cpm_iface_is_valid_req(cpm, cpm_req))
+		return -EINVAL;
+
+	header = cpm_req_get_header(client, cpm_req);
+	if (!header)
+		return -EINVAL;
+
+	return __cpm_send_message(cpm, cpm_req, header, client->src_id);
+}
+EXPORT_SYMBOL_GPL(cpm_send_message);
+
+/**
+ * cpm_req_tx_done - callback function from request channels
+ * @client: Pointer to the mailbox client structure.
+ * @msg: Pointer to the transmitted mailbox message.
+ * @r: Status of the transmission.
+ */
+static void cpm_req_tx_done(struct mbox_client *client, void *msg, int r)
+{
+	struct cpm_session *session;
+
+	if (!msg)
+		return;
+
+	session = container_of(msg, struct cpm_session, req_mba_msg);
+
+	trace_cpm_req_tx_done(session);
+
+	if (cpm_is_oneway_header(&session->req_msg) ||
+	    cpm_is_response_header(&session->req_msg))
+		complete(&session->complete);
+
+	kref_put(&session->refcount, cpm_session_release);
+}
+
+static inline u32 cpm_iface_prepare_resp_context(u32 header)
+{
+	u32 src_id = goog_mba_q_get_dst_id(&header);
+	u32 dst_id = goog_mba_q_get_src_id(&header);
+	u32 token = goog_mba_q_get_token(&header);
+
+	return goog_mba_q_create_hdr(src_id, dst_id, 0, token,
+				     GOOG_MBA_Q_TYPE_RESPONSE);
+}
+
+static void cpm_handle_host_tx(struct cpm_data *cpm, const void *msg)
+{
+	struct device *dev = cpm->dev;
+	const struct cpm_payload *msg_payload = msg;
+	struct cpm_client *client;
+	int dst_id = goog_mba_q_get_dst_id(msg);
+	u32 resp_context = 0;
+
+	if (cpm_is_request_header(msg))
+		resp_context = cpm_iface_prepare_resp_context(msg_payload->header);
+
+	scoped_guard(spinlock_irqsave, &cpm->lock) {
+		client = cpm_search_client_by_id(cpm, dst_id);
+		if (IS_ERR_OR_NULL(client) || !client->host_tx_cb) {
+			dev_err(dev, "No available client callback (id:%d).\n Payload Info:\n",
+				dst_id);
+			print_hex_dump(KERN_ERR, dev_name(dev), DUMP_PREFIX_NONE, 16, 4,
+				       msg_payload, sizeof(*msg_payload), false);
+			return;
+		}
+
+		client->host_tx_cb(resp_context, msg_payload, client->priv);
+	}
+
+	trace_cpm_handle_host_tx(dst_id, resp_context, msg_payload);
+}
+
+/**
+ * cpm_resp_cb - Handles incoming messages on response channels
+ * @mbox_client: Pointer to the mailbox client structure.
+ * @param: Pointer to the received mailbox message (&struct goog_mba_rx_msg).
+ *
+ * This callback is invoked when a message arrives on a response channel. It
+ * handles the following scenarios:
+ *
+ * 1. CPM Response to Client Request: Processes a response from the CPM to a
+ *    previously issued client request.
+ * 2. CPM-Initiated Request: Processes a new request initiated by the CPM and
+ *    dispatches it to the appropriate client handler.
+ * 3. Invalid Message Type: Handles unexpected message types.
+ */
+static void cpm_resp_cb(struct mbox_client *mbox_client, void *param)
+{
+	struct goog_mba_rx_msg *rx_msg = param;
+	const void *msg = rx_msg->payload;
+	struct cpm_mbox_chan_info *chan_info =
+		container_of(mbox_client, struct cpm_mbox_chan_info, client);
+	struct cpm_data *cpm = container_of(chan_info, struct cpm_data, resp_chan);
+	struct device *dev = cpm->dev;
+
+	if (cpm_is_init_header(msg)) {
+		/*
+		 * This is unexpected. If we see this, we may need to initiate
+		 * our own (re)init message -- cpm_init_q_mode().
+		 */
+		dev_err(dev, "Reset message received from the remote\n");
+		return;
+	}
+
+	if (cpm_is_response_header(msg)) {
+		const struct cpm_payload *msg_payload = msg;
+		bool found;
+
+		scoped_guard(spinlock_irqsave, &cpm->lock)
+			found = cpm_remove_pending_resp(cpm, msg_payload);
+		if (!found)
+			dev_err(dev,
+				"No token:%u found for response payload: %#10x %#10x %#10x %#10x\n",
+				goog_mba_q_get_token(&msg_payload->header),
+				msg_payload->header, msg_payload->payload[0],
+				msg_payload->payload[1], msg_payload->payload[2]);
+	} else if (cpm_is_request_header(msg) || cpm_is_oneway_header(msg)) {
+		cpm_handle_host_tx(cpm, msg);
+	} else {
+		dev_err(dev, "Unknown message type: %u\n", goog_mba_q_get_type(msg));
+	}
+}
+
+static void cpm_iface_free_chan_action(void *data)
+{
+	struct cpm_mbox_chan_info *chan = data;
+
+	mbox_free_channel(chan->chan);
+}
+
+static int
+cpm_init_channel(struct cpm_data *cpm,
+		 struct cpm_mbox_chan_info *chan_info,
+		 int idx,
+		 void (*txdone_callback)(struct mbox_client *client, void *msg, int r),
+		 void (*rx_callback)(struct mbox_client *client, void *msg))
+{
+	struct device *dev = cpm->dev;
+	struct mbox_client *client;
+
+	client = &chan_info->client;
+	client->dev = dev;
+	client->rx_callback = rx_callback;
+	client->tx_done = txdone_callback;
+
+	chan_info->chan = mbox_request_channel(client, idx);
+	if (IS_ERR(chan_info->chan))
+		return PTR_ERR(chan_info->chan);
+
+	return devm_add_action_or_reset(dev, cpm_iface_free_chan_action, chan_info);
+}
+
+/**
+ * cpm_init_req_channels - Request all available TX mailbox channels
+ * @cpm: Core driver data.
+ *
+ * The Linux mailbox core only allows a single in-flight message per channel.
+ * To utilize the underlying mailbox controller's hardware queue, the
+ * controller exposes one mailbox channel per hardware queue slot (N channels
+ * total) for the same devicetree node (with #mbox-cells = <0>) and hands out
+ * the next unbound channel on each mbox_request_channel() call. Repeatedly
+ * request MBOX_IDX_REQ until all hardware queue slots have been claimed
+ * (-EBUSY).
+ *
+ * cpm_try_submit() then rotates through @cpm->req_chans round-robin under
+ * @cpm->lock. Because the hardware queue is strictly FIFO, the m-th message is
+ * always assigned to channel (m mod N):
+ * - When fewer than N messages are in flight in hardware, the next channel in
+ *   the rotation is guaranteed to already be idle and submits straight to
+ *   hardware.
+ * - When all N hardware slots are busy, the next channel in the rotation holds
+ *   the oldest in-flight message and will be the first to complete, so
+ *   queuing into that channel's mailbox-core software queue drains messages
+ *   into hardware in FIFO order.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int cpm_init_req_channels(struct cpm_data *cpm)
+{
+	struct device *dev = cpm->dev;
+	int ret;
+
+	while (true) {
+		struct cpm_mbox_chan_info *chan_info;
+
+		chan_info = devm_kzalloc(dev, sizeof(*chan_info), GFP_KERNEL);
+		if (!chan_info)
+			return -ENOMEM;
+
+		ret = cpm_init_channel(cpm, chan_info, MBOX_IDX_REQ,
+				       cpm_req_tx_done, NULL);
+		if (ret) {
+			devm_kfree(dev, chan_info);
+			if (ret == -EBUSY && !list_empty(&cpm->req_chans))
+				return 0;
+			return dev_err_probe(dev, ret,
+					     "Failed to request TX mailbox channel\n");
+		}
+
+		list_add_tail(&chan_info->entry, &cpm->req_chans);
+	}
+}
+
+static int cpm_init_q_mode_proto(struct cpm_data *cpm)
+{
+	struct cpm_payload msg_payload = {};
+	struct cpm_request req = {
+		.msg_type = ONEWAY_MSG,
+		.req_msg = &msg_payload,
+	};
+
+	return __cpm_send_message(cpm, &req, GOOG_MBA_Q_INIT_PROTO_VAL, 0);
+}
+
+static int cpm_init_q_mode(struct cpm_data *cpm)
+{
+	int ret;
+
+	ret = cpm_init_q_mode_proto(cpm);
+	if (ret < 0)
+		return ret;
+
+	cpm->seq_num = GOOG_MBA_Q_MIN_SEQ_VAL;
+
+	return 0;
+}
+
+static void cpm_adev_release(struct device *dev)
+{
+	struct auxiliary_device *adev = to_auxiliary_dev(dev);
+
+	of_node_put(dev->of_node);
+	kfree(adev);
+}
+
+static void cpm_unregister_adev_action(void *data)
+{
+	struct auxiliary_device *adev = data;
+
+	auxiliary_device_delete(adev);
+	auxiliary_device_uninit(adev);
+}
+
+static int cpm_register_adev(struct cpm_data *cpm, const char *name)
+{
+	struct auxiliary_device *adev;
+	int ret;
+
+	adev = kzalloc_obj(*adev, GFP_KERNEL);
+	if (!adev)
+		return -ENOMEM;
+
+	adev->name = name;
+	adev->id = 0;
+	adev->dev.parent = cpm->dev;
+	adev->dev.release = cpm_adev_release;
+	device_set_of_node_from_dev(&adev->dev, cpm->dev);
+
+	ret = auxiliary_device_init(adev);
+	if (ret) {
+		of_node_put(adev->dev.of_node);
+		kfree(adev);
+		return ret;
+	}
+
+	ret = auxiliary_device_add(adev);
+	if (ret) {
+		auxiliary_device_uninit(adev);
+		return ret;
+	}
+
+	return devm_add_action_or_reset(cpm->dev, cpm_unregister_adev_action, adev);
+}
+
+static const char * const cpm_adev_names[] = {
+	"reset",
+	"power-controller",
+	"clk",
+	"irq",
+};
+
+static int cpm_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct cpm_data *cpm;
+	int ret;
+
+	cpm = devm_kzalloc(dev, sizeof(*cpm), GFP_KERNEL);
+	if (!cpm)
+		return -ENOMEM;
+
+	cpm->dev = dev;
+	platform_set_drvdata(pdev, cpm);
+
+	spin_lock_init(&cpm->lock);
+	INIT_LIST_HEAD(&cpm->client_list_head);
+	INIT_LIST_HEAD(&cpm->req_chans);
+
+	ret = cpm_init_req_channels(cpm);
+	if (ret)
+		return ret;
+
+	ret = cpm_init_channel(cpm, &cpm->resp_chan, MBOX_IDX_RESP,
+			       NULL, cpm_resp_cb);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to request RX mailbox channel\n");
+
+	ret = cpm_init_q_mode(cpm);
+	if (ret < 0)
+		return ret;
+
+	for (int i = 0; i < ARRAY_SIZE(cpm_adev_names); i++) {
+		ret = cpm_register_adev(cpm, cpm_adev_names[i]);
+		if (ret) {
+			dev_err(dev, "failed to register %s auxiliary device: %d\n",
+				cpm_adev_names[i], ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static struct platform_driver cpm_driver = {
+	.probe = cpm_probe,
+	.driver = {
+		.name = "google-cpm-mba",
+		.of_match_table = cpm_of_match_table,
+	},
+};
+
+module_platform_driver(cpm_driver);
+
+MODULE_DESCRIPTION("Google MBA CPM interface");
+MODULE_AUTHOR("Lucas Wei <lucaswei@google.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/google/google-cpm-mba.h b/drivers/soc/google/google-cpm-mba.h
new file mode 100644
index 000000000000..425fe3171085
--- /dev/null
+++ b/drivers/soc/google/google-cpm-mba.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+
+#ifndef _GOOGLE_CPM_MBA_PRIV_H_
+#define _GOOGLE_CPM_MBA_PRIV_H_
+
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include <soc/google/google-cpm-mba.h>
+
+/**
+ * struct cpm_client - Represents a registered CPM interface client
+ * @dev: The client's associated device.
+ * @src_id: The client's source ID within the CPM system.
+ * @host_tx_cb: Callback function invoked for CPM-initiated transactions.
+ * @entry: Internal field for managing the list of active clients.
+ * @priv: Client-specific data passed to the @host_tx_cb callback.
+ */
+struct cpm_client {
+	struct device *dev;
+	int src_id;
+	cpm_cb_t host_tx_cb;
+	struct list_head entry;
+	void *priv;
+};
+
+#endif /* _GOOGLE_CPM_MBA_PRIV_H_ */
diff --git a/include/soc/google/goog_cpm_service_ids.h b/include/soc/google/goog_cpm_service_ids.h
new file mode 100644
index 000000000000..c28766fe6ba8
--- /dev/null
+++ b/include/soc/google/goog_cpm_service_ids.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2022-2026 Google LLC
+ */
+
+#ifndef __GOOG_CPM_SERVICE_ID_H
+#define __GOOG_CPM_SERVICE_ID_H
+
+enum apc_common_mba_service_id {
+	APC_COMMON_SERVICE_FIRST		= 0x0,
+	APC_COMMON_SERVICE_ID_RESERVED		= 0x0,
+	APC_COMMON_SERVICE_ID_PING		= 0x1,
+	APC_COMMON_SERVICE_ID_CPMLOG		= 0x2,
+	APC_COMMON_SERVICE_ID_GMC		= 0x3,
+	APC_COMMON_SERVICE_ID_GSLC		= 0x4,
+	APC_COMMON_SERVICE_ID_CPM_TRACEPOINT	= 0x5,
+	APC_COMMON_SERVICE_ID_PF_STATE_CPM	= 0x6,
+	APC_COMMON_SERVICE_ID_POWER_CONTROLLER	= 0x7,
+	APC_COMMON_SERVICE_ID_MIPM		= 0x8,
+	APC_COMMON_SERVICE_LAST			= 0x37,
+};
+
+#define MBA_COMMON_MIN_SERVICE_ID		0x38
+#define MBA_COMMON_MAX_SERVICE_ID		0x70
+
+enum cpm_common_mba_service_id {
+	CPM_COMMON_SERVICE_FIRST		= 0x0,
+	/* Test service */
+	CPM_COMMON_PING_SERVICE			= 0x0,
+	/* Used for extracting logs */
+	CPM_AP_COMMON_LOG_SERVICE		= 0x1,
+	/* Interact with tracepoint library */
+	CPM_AP_COMMON_TRACEPOINT_SERVICE	= 0x2,
+	/* Control GSLC CPM functions */
+	CPM_AP_COMMON_GSLC_MB_SERVICE		= 0x3,
+	/* Control GMCCPM functions */
+	CPM_AP_COMMON_GMC_MB_SERVICE		= 0x4,
+	/* Control LPB peripheral */
+	CPM_COMMON_LPB_SERVICE			= 0x5,
+	/* System reset service */
+	CPM_COMMON_RESET_SERVICE		= 0x6,
+	/* ODPM API service for AP */
+	CPM_AP_COMMON_ODPM_MB_SERVICE		= 0x7,
+	/* LPCM service. */
+	CPM_COMMON_LPCM_SERVICE			= 0x8,
+	/* Control and modify SoC power states */
+	CPM_COMMON_POWER_STATE_SERVICE		= 0x9,
+	/* PMIC related services */
+	CPM_COMMON_PMIC_SERVICE			= 0xA,
+	/* MIPM interface */
+	CPM_COMMON_MIPM_SERVICE			= 0xB,
+	/* Thermal services - emul temps, loops, config, etc */
+	CPM_COMMON_THERMAL_SERVICE		= 0xC,
+	/* Pull PIF history status over LMF. */
+	CPM_COMMON_PIF_HISTORY_SERVICE		= 0xD,
+	/* Pwrblk/SSWRP based service */
+	CPM_COMMON_PWRBLK_SERVICE		= 0xE,
+	/* CPM command line service */
+	CPM_COMMON_COMMAND_LINE_SERVICE		= 0xF,
+	/* GDMC->CPM special message */
+	CPM_GDMC_PMIC_SERVICE			= 0x10,
+	/* SMMU initial register configuration service */
+	CPM_SMMU_SERVICE			= 0x11,
+	/* BCL service */
+	CPM_COMMON_BCL_SERVICE			= 0x12,
+	CPM_COMMON_THERMAL_V2_SERVICE		= 0x15,
+	/* Pixel firmware tracepoint service */
+	CPM_COMMON_FWTP_SERVICE			= 0x17,
+	CPM_COMMON_SERVICE_LAST			= 0x37,
+};
+
+enum cpm_ap_ns_mba_service_id {
+	CPM_AP_NS_MAILBOX_SERVICE_LAST		= 0x38,
+	CPM_AP_NS_SSRAM_SERVICE			= 0x66,
+	CPM_AP_NS_SPMI_SERVICE			= 0x67,
+	CPM_AP_NS_POWER_DASH_MB_SERVICE		= 0x68,
+	CPM_AP_NS_ODPM_MB_SERVICE		= 0x69,
+	CPM_AP_NS_IRQ_SERVICE			= 0x6E,
+	/* Control latency library CPM functions */
+	CPM_AP_NS_LTC_MB_SERVICE		= 0x70,
+	CPM_AP_NS_MAILBOX_SERVICE_FIRST		= 0x70,
+};
+
+#endif /* __GOOG_CPM_SERVICE_ID_H */
diff --git a/include/soc/google/google-cpm-mba.h b/include/soc/google/google-cpm-mba.h
new file mode 100644
index 000000000000..16789f71a624
--- /dev/null
+++ b/include/soc/google/google-cpm-mba.h
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+
+#ifndef _GOOGLE_CPM_MBA_H_
+#define _GOOGLE_CPM_MBA_H_
+
+#include <linux/types.h>
+
+struct cpm_client;
+
+/**
+ * enum cpm_msg_type - Specifies the type of message for CPM interface transactions
+ * @ONEWAY_MSG:   A one-way message that does not expect a response. The @resp_msg
+ *                field in &struct cpm_request is ignored.
+ * @REQUEST_MSG:  A request message that expects a response.
+ * @RESPONSE_MSG: A response message to a previous request.
+ */
+enum cpm_msg_type {
+	ONEWAY_MSG,
+	REQUEST_MSG,
+	RESPONSE_MSG,
+};
+
+#define GOOG_MBA_PAYLOAD_SIZE 3
+
+/**
+ * struct cpm_payload - Represents a payload for CPM messages
+ * @header:  A header containing message metadata (e.g., type, destination).
+ *           Client shouldn't fill the header.
+ * @payload: An array of 32-bit words representing the actual payload data.
+ */
+struct cpm_payload {
+	u32 header;
+	u32 payload[GOOG_MBA_PAYLOAD_SIZE];
+};
+
+/**
+ * struct cpm_request - Represents a request within the CPM interface
+ * @msg_type:     Specifies the message type (see &enum cpm_msg_type).
+ * @req_msg:      The request message payload.
+ * @resp_msg:     Buffer to receive a response (effective when @msg_type is REQUEST_MSG).
+ * @dst_id:       Destination service ID within the CPM host.
+ * @tout_ms:      Request timeout in milliseconds (0 indicates no timeout).
+ * @resp_context: Context for responding to CPM-initiated messages (used when
+ *                @msg_type is RESPONSE_MSG).
+ */
+struct cpm_request {
+	enum cpm_msg_type msg_type;
+	struct cpm_payload *req_msg;
+	struct cpm_payload *resp_msg;
+	int dst_id;
+	unsigned long tout_ms;
+	u32 resp_context;
+};
+
+/**
+ * typedef cpm_cb_t - Callback invoked for CPM-initiated transactions
+ * @context:   Context value provided by the CPM host.
+ *             0 for oneway request, otherwise client need to respond.
+ * @msg:       Pointer to the message payload received from the CPM host.
+ * @priv:      Client's private data (registered via cpm_request_client()).
+ *
+ * A client may register a callback while calling cpm_request_client(). Clients
+ * will be notified for CPM initiated request that match their src_id. @context
+ * indicates whether clients need to respond to CPM's request: ONEWAY_MSG for
+ * one-way requests from CPM; otherwise, clients needs to respond with msg_type
+ * assigned to RESPONSE_MSG.
+ *
+ * Callbacks may be called in an atomic context.
+ */
+typedef void (*cpm_cb_t)(u32 context, const struct cpm_payload *msg, void *priv);
+
+/**
+ * cpm_request_client() - Registers a client with the CPM interface
+ * @dev:       Client's device structure.
+ * @src_id:    Client's service ID for identification by CPM (optional).
+ *             Register client's service ID (1 to 255)
+ *             with callback in @cpm_cb to handle CPM initiated request.
+ *             Otherwise, specify 0.
+ * @cpm_cb:    Callback function invoked for CPM-initiated requests (optional).
+ * @priv:      Private data passed to the callback function (optional).
+ *
+ * This function initializes a CPM interface handle for the client, allowing the
+ * client to interact with the CPM. The handle encapsulates client-specific
+ * information for use by the CPM interface.
+ *
+ * Requirements:
+ *  * The client device must be an auxiliary_device child of the CPM device.
+ *  * Call cpm_free_client() when the handle is no longer needed, or just use
+ *    the devm_* variant.
+ *
+ * Return: A &struct cpm_client handle on success, or an ERR_PTR() on failure.
+ *         -ENODEV: invalid device structure (see details in kernel log).
+ *         -EBUSY: client has registered with the same @src_id.
+ *         -EINVAL: client bind to wrong phandle that CPM interface don't support.
+ *         -ENOMEM: memory allocation failed.
+ */
+struct cpm_client *cpm_request_client(struct device *dev,
+				      int src_id,
+				      cpm_cb_t cpm_cb,
+				      void *priv);
+
+/**
+ * devm_cpm_request_client() - Managed version of cpm_request_client()
+ * @dev:       Client's device structure.
+ * @src_id:    Client's service ID for identification by CPM (optional).
+ * @cpm_cb:    Callback function invoked for CPM-initiated requests (optional).
+ * @priv:  Private data passed to the callback function (optional).
+ *
+ * Return: A &struct cpm_client handle on success, or an ERR_PTR() on failure.
+ *         See cpm_request_client() for details.
+ */
+struct cpm_client *devm_cpm_request_client(struct device *dev,
+					   int src_id,
+					   cpm_cb_t cpm_cb,
+					   void *priv);
+
+/**
+ * cpm_free_client() - Free resources and refcount from CPM interface device
+ * @client: Handle of &struct cpm_client used for CPM message operations.
+ */
+void cpm_free_client(struct cpm_client *client);
+
+/**
+ * cpm_send_message() - Send a payload to CPM host
+ * @client:      Client handle obtained from cpm_request_client().
+ * @cpm_req:     Request structure containing message parameters.
+ *
+ * This function enables clients to transmit messages to the CPM host. It handles
+ * the following scenarios:
+ *
+ * * **One-way Messages:**        Returns after Tx done or timeout.
+ * * **Request Messages:**        Returns after receiving a response or timeout.
+ * * **CPM-Initiated Responses:** Transmits a response using the provided `resp_context`.
+ *
+ * Important Notes:
+ * * The first word of the request payload is used by the transport layer.
+ * * For request messages, the `resp_msg` field will hold the CPM host's response.
+ * * Do not call this function from atomic contexts due to potential blocking.
+ *
+ * Return: 0 on success; negative error code on failure.
+ */
+int cpm_send_message(struct cpm_client *client, struct cpm_request *cpm_req);
+
+#endif /* _GOOGLE_CPM_MBA_H_ */
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 4/9] pmdomain: google: Add CPM power-domain driver
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (2 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 3/9] soc: google: Add CPM over MailBox Array driver Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-26 21:43 ` [PATCH 5/9] clk: google: Add CPM clock driver Brian Norris
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Central Power Manager (CPM) is a co-processor on the Google Tensor
SoCs, as used on Pixel phones. Among other features, CPM manages most of
the power domains on these SoCs. We talk to the CPM over a pair of
mailboxes, and we hide many of these details beneath a
cpm_send_message() API.

Tensor SoCs consist of various regions called Subsystem Wrappers
(SSWRP). SSWRPs and their sub-domains can be powered on and off by
interfacing with the Local Power and Clock Manager (LPCM) associated
with each SSWRP. Thus, the power-domain mapping tends to consist of a
"LPCM ID" (the SSWRP) and a "subdomain ID", although the mappings are
complicated enough we simply expose a single "power domain ID" in the
device tree binding, and figure out the rest of the details by per-SoC
tables.

Most of the complexity in this driver is in the hardware tables, and the
mappings between domains.

Because it may be useful both for reviewers familiar with the downstream
Linux support for these devices, or for future developers looking at
adapting/improving/adding further features later on, it's worth noting
some major deviations from the downstream drivers:

 * Use auxiliary_bus. The whole CPM-interface structure was reworked for
   upstream Linux, so rather than a bunch of DT nodes and platform
   devices, most/all CPM services are simply instantiated as auxiliary
   devices from the CPM driver, and share the same CPM DT node.

 * As a corollary to the DT restructuring: we eliminate the use of
   per-domain DT nodes. Downstream DTs would describe each power domain
   as its own node (with #power-domain-cells = <0>), and a variety of
   custom DT properties ("irq-safe", "always-on"). These are all encoded
   in the driver's per-SoC tables instead, and devices simply use a
   single cell (power domain ID) to refer to which domain they use.

 * Use devm more heavily

 * Drop 'no-auto-resume' feature. This was a Pixel optimization to avoid
   excessive power-on latency during system resume. It required unsound
   overrides to basic genpd functionality. I assume it should be
   replaced by more aggressive use of direct-complete eventually.

 * Drop CPM_LGA_LSIO_S_CLI_GPIO_IRQ_SAFE_ID,
   CPM_LGA_LSIO_N_CLI_GPIO_IRQ_SAFE_ID, and
   CPM_LGA_LSIO_E_CLI_GPIO_IRQ_SAFE_ID. They were not actually used
   downstream.

 * Make our debugfs file readable (downstream has read/write operations,
   but leaves it mode 0220)

 * Replace a downstream "GENPD_FLAG_ALWAYS_ON" (but only during system
   suspend) feature with simply applying GENPD_FLAG_ACTIVE_WAKEUP to the
   SSWRP_AOC domain. This domain is for an "Always On Compute" subsystem
   on the SoC, but it also can be powered off temporarily for reset or
   similar. Any drivers for this component should simply manage its
   wakeup status properly, to ensure it stays "on" in the appropriate
   system suspend states.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 drivers/pmdomain/Kconfig                  |   1 +
 drivers/pmdomain/Makefile                 |   1 +
 drivers/pmdomain/google/Kconfig           |  14 +
 drivers/pmdomain/google/Makefile          |   2 +
 drivers/pmdomain/google/cpm-power-lga.h   | 467 ++++++++++++++++
 drivers/pmdomain/google/cpm-power-trace.h |  80 +++
 drivers/pmdomain/google/cpm-power.c       | 637 ++++++++++++++++++++++
 drivers/pmdomain/google/lpb_service.h     | 200 +++++++
 8 files changed, 1402 insertions(+)
 create mode 100644 drivers/pmdomain/google/Kconfig
 create mode 100644 drivers/pmdomain/google/Makefile
 create mode 100644 drivers/pmdomain/google/cpm-power-lga.h
 create mode 100644 drivers/pmdomain/google/cpm-power-trace.h
 create mode 100644 drivers/pmdomain/google/cpm-power.c
 create mode 100644 drivers/pmdomain/google/lpb_service.h

diff --git a/drivers/pmdomain/Kconfig b/drivers/pmdomain/Kconfig
index 5341dff669be..aeda621411df 100644
--- a/drivers/pmdomain/Kconfig
+++ b/drivers/pmdomain/Kconfig
@@ -6,6 +6,7 @@ source "drivers/pmdomain/amlogic/Kconfig"
 source "drivers/pmdomain/apple/Kconfig"
 source "drivers/pmdomain/arm/Kconfig"
 source "drivers/pmdomain/bcm/Kconfig"
+source "drivers/pmdomain/google/Kconfig"
 source "drivers/pmdomain/imx/Kconfig"
 source "drivers/pmdomain/marvell/Kconfig"
 source "drivers/pmdomain/mediatek/Kconfig"
diff --git a/drivers/pmdomain/Makefile b/drivers/pmdomain/Makefile
index d7aee13ae571..608555ba5990 100644
--- a/drivers/pmdomain/Makefile
+++ b/drivers/pmdomain/Makefile
@@ -4,6 +4,7 @@ obj-y					+= amlogic/
 obj-y					+= apple/
 obj-y					+= arm/
 obj-y					+= bcm/
+obj-y					+= google/
 obj-y					+= imx/
 obj-y					+= marvell/
 obj-y					+= mediatek/
diff --git a/drivers/pmdomain/google/Kconfig b/drivers/pmdomain/google/Kconfig
new file mode 100644
index 000000000000..e2a9bf0a7c82
--- /dev/null
+++ b/drivers/pmdomain/google/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config GOOGLE_CPM_POWER
+	tristate "Google Tensor CPM power controller driver"
+	depends on ARCH_GOOGLE || COMPILE_TEST
+	depends on GOOGLE_CPM_MBA
+	select PM_GENERIC_DOMAINS
+	default ARCH_GOOGLE
+	help
+	  This driver allows clients to power on/off power domains on Google
+	  Tensor SoCs via the generic power domain framework. It uses SMC or
+	  CPM mailbox array to request toggling the power domains.
+
+	  Say Y or M here to support CPM power domains.
diff --git a/drivers/pmdomain/google/Makefile b/drivers/pmdomain/google/Makefile
new file mode 100644
index 000000000000..34ea111208df
--- /dev/null
+++ b/drivers/pmdomain/google/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_GOOGLE_CPM_POWER) += cpm-power.o
diff --git a/drivers/pmdomain/google/cpm-power-lga.h b/drivers/pmdomain/google/cpm-power-lga.h
new file mode 100644
index 000000000000..fc006aeb5cfe
--- /dev/null
+++ b/drivers/pmdomain/google/cpm-power-lga.h
@@ -0,0 +1,467 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+#ifndef _CPM_POWER_LGA_H
+#define _CPM_POWER_LGA_H
+
+#include <dt-bindings/power/google,lga-cpm.h>
+#include <linux/kernel.h>
+#include <linux/pm_domain.h>
+#include <linux/types.h>
+
+#include "lpb_service.h"
+
+#define NOT_SUPPORTED				-1
+
+/* CPM Mapping Definitions */
+enum cpm_lga_sswrp_id {
+	CPM_LGA_SSWRP_AOC_ID,
+	CPM_LGA_SSWRP_AURORA_ID,
+	CPM_LGA_SSWRP_CODEC_3P_ID,
+	CPM_LGA_SSWRP_CPU_ID,
+	CPM_LGA_SSWRP_CPUACC_ID,
+	CPM_LGA_SSWRP_DPU_ID,
+	CPM_LGA_SSWRP_EH_ID,
+	CPM_LGA_SSWRP_FABHBW_ID,
+	CPM_LGA_SSWRP_FABMED_ID,
+	CPM_LGA_SSWRP_FABRT_ID,
+	CPM_LGA_SSWRP_FABSTBY_ID,
+	CPM_LGA_SSWRP_FABSYSS_ID,
+	CPM_LGA_SSWRP_G2D_ID,
+	CPM_LGA_SSWRP_GMC0_ID,
+	CPM_LGA_SSWRP_GMC1_ID,
+	CPM_LGA_SSWRP_GMC2_ID,
+	CPM_LGA_SSWRP_GMC3_ID,
+	CPM_LGA_SSWRP_GPCA_ID,
+	CPM_LGA_SSWRP_GPCM_ID,
+	CPM_LGA_SSWRP_GPDMA_ID,
+	CPM_LGA_SSWRP_GPU_ID,
+	CPM_LGA_SSWRP_GSA_ID,
+	CPM_LGA_SSWRP_GSW_ID,
+	CPM_LGA_SSWRP_HSIO_N_ID,
+	CPM_LGA_SSWRP_HSIO_S_ID,
+	CPM_LGA_SSWRP_INF_TCU_ID,
+	CPM_LGA_SSWRP_ISPBE_ID,
+	CPM_LGA_SSWRP_ISPFE_ID,
+	CPM_LGA_SSWRP_LSIO_E_ID,
+	CPM_LGA_SSWRP_LSIO_N_ID,
+	CPM_LGA_SSWRP_LSIO_S_ID,
+	CPM_LGA_SSWRP_MEMSS_ID,
+	CPM_LGA_SSWRP_TPU_ID,
+	CPM_LGA_SSWRP_COUNT,
+};
+
+#define CPM_LGA_GPU_CORE_LOGIC_ID		0
+
+#define CPM_LGA_HSIO_N_USB_ID			0
+#define CPM_LGA_HSIO_N_PCIE_ID			1
+#define CPM_LGA_HSIO_N_EBU_ID			2
+#define CPM_LGA_HSIO_N_EBU_CORE_ID		3
+#define CPM_LGA_HSIO_N_DP_ID			4
+#define CPM_LGA_HSIO_N_USB_PSW_ID		5
+
+#define CPM_LGA_HSIO_S_UFS_ID			0
+#define CPM_LGA_HSIO_S_PCIE_ID			1
+#define CPM_LGA_HSIO_S_SD_ID			2
+#define CPM_HSIO_S_UFS_PREP_ID			3
+
+#define CPM_LGA_LSIO_CLI_GPIO_ID		0
+
+#define CPM_LGA_DPU_FE0_ID			0
+#define CPM_LGA_DPU_FE1_ID			1
+#define CPM_LGA_DPU_DSI0_ID			2
+#define CPM_LGA_DPU_DSI1_ID			3
+#define CPM_LGA_DPU_DP_ID			4
+
+#define CPM_LGA_ISPFE_TOP_ID			0
+#define CPM_LGA_ISPFE_PIPE_ID			1
+#define CPM_LGA_ISPFE_CSIS_ID			2
+#define CPM_LGA_ISPBE_TOP_ID			0
+
+#define CPM_LGA_GSW_MAIN_ID			0
+
+#define CPM_LGA_G2D_CORE_ID			0
+
+#define CPM_LGA_C3P_CORE_ID			0
+
+#define CPM_LGA_GPDMA_ID			0
+
+#define CPM_LGA_INF_TCU_ID			0
+
+#define CPM_LGA_AOC_CORE_ID			0
+
+struct cpm_power_domain_desc {
+	const char *name;
+	int sswrp_id;
+	int subdomain_id;
+	/* GENPD_FLAG_* to apply. */
+	u32 flags;
+	/* Signifies that the power domain is already on at boot time. */
+	bool on_at_init;
+	/* Control the power domain via SMC calls instead of CPM requests. */
+	bool use_smc;
+	/* Use NOT_SUPPORTED for top-level (SSWRP) domains. */
+	int parent_id;
+};
+
+#define LPCM_GENPD_SSWRP_DPU			0
+#define LPCM_GENPD_SSWRP_HSIO_S			1
+#define LPCM_GENPD_SSWRP_HSIO_N			2
+#define LPCM_GENPD_SSWRP_LSIO_S			3
+#define LPCM_GENPD_SSWRP_LSIO_N			4
+#define LPCM_GENPD_SSWRP_LSIO_E			5
+#define LPCM_GENPD_SSWRP_G2D			6
+#define LPCM_GENPD_SSWRP_GPDMA			7
+#define LPCM_GENPD_SSWRP_ISPFE			8
+#define LPCM_GENPD_SSWRP_AOC			9
+
+static const int lga_sswrp_to_lpb[CPM_LGA_SSWRP_COUNT] = {
+	[CPM_LGA_SSWRP_AOC_ID]			= LPB_SERVICE_AOC_ID,
+	[CPM_LGA_SSWRP_AURORA_ID]		= LPB_SERVICE_AURORA_ID,
+	[CPM_LGA_SSWRP_CODEC_3P_ID]		= LPB_SERVICE_CODEC_3P_ID,
+	[CPM_LGA_SSWRP_CPU_ID]			= LPB_SERVICE_CPU_ID,
+	[CPM_LGA_SSWRP_CPUACC_ID]		= LPB_SERVICE_CPUACC_ID,
+	[CPM_LGA_SSWRP_DPU_ID]			= LPB_SERVICE_DPU_ID,
+	[CPM_LGA_SSWRP_EH_ID]			= LPB_SERVICE_EH_ID,
+	[CPM_LGA_SSWRP_FABHBW_ID]		= LPB_SERVICE_FABHBW_ID,
+	[CPM_LGA_SSWRP_FABMED_ID]		= LPB_SERVICE_FABMED_ID,
+	[CPM_LGA_SSWRP_FABRT_ID]		= LPB_SERVICE_FABRT_ID,
+	[CPM_LGA_SSWRP_FABSTBY_ID]		= LPB_SERVICE_FABSTBY_ID,
+	[CPM_LGA_SSWRP_FABSYSS_ID]		= LPB_SERVICE_FABSYSS_ID,
+	[CPM_LGA_SSWRP_G2D_ID]			= LPB_SERVICE_G2D_ID,
+	[CPM_LGA_SSWRP_GMC0_ID]			= LPB_SERVICE_GMC0_ID,
+	[CPM_LGA_SSWRP_GMC1_ID]			= LPB_SERVICE_GMC1_ID,
+	[CPM_LGA_SSWRP_GMC2_ID]			= LPB_SERVICE_GMC2_ID,
+	[CPM_LGA_SSWRP_GMC3_ID]			= LPB_SERVICE_GMC3_ID,
+	[CPM_LGA_SSWRP_GPCA_ID]			= LPB_SERVICE_GPCA_ID,
+	[CPM_LGA_SSWRP_GPCM_ID]			= LPB_SERVICE_GPCM_ID,
+	[CPM_LGA_SSWRP_GPDMA_ID]		= LPB_SERVICE_GPDMA_ID,
+	[CPM_LGA_SSWRP_GPU_ID]			= LPB_SERVICE_GPU_ID,
+	[CPM_LGA_SSWRP_GSA_ID]			= LPB_SERVICE_GSA_ID,
+	[CPM_LGA_SSWRP_GSW_ID]			= LPB_SERVICE_GSW_ID,
+	[CPM_LGA_SSWRP_HSIO_N_ID]		= LPB_SERVICE_HSIO_N_ID,
+	[CPM_LGA_SSWRP_HSIO_S_ID]		= LPB_SERVICE_HSIO_S_ID,
+	[CPM_LGA_SSWRP_INF_TCU_ID]		= LPB_SERVICE_INF_TCU_ID,
+	[CPM_LGA_SSWRP_ISPBE_ID]		= LPB_SERVICE_ISPBE_ID,
+	[CPM_LGA_SSWRP_ISPFE_ID]		= LPB_SERVICE_ISPFE_ID,
+	[CPM_LGA_SSWRP_LSIO_E_ID]		= LPB_SERVICE_LSIO_E_ID,
+	[CPM_LGA_SSWRP_LSIO_N_ID]		= LPB_SERVICE_LSIO_N_ID,
+	[CPM_LGA_SSWRP_LSIO_S_ID]		= LPB_SERVICE_LSIO_S_ID,
+	[CPM_LGA_SSWRP_MEMSS_ID]		= LPB_SERVICE_MEMSS_ID,
+	[CPM_LGA_SSWRP_TPU_ID]			= LPB_SERVICE_TPU_ID,
+};
+
+static const int lga_sswrp_to_lpcm[CPM_LGA_SSWRP_COUNT] = {
+	[CPM_LGA_SSWRP_AOC_ID]			= LPCM_GENPD_SSWRP_AOC,
+	[CPM_LGA_SSWRP_AURORA_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_CODEC_3P_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_CPU_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_CPUACC_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_DPU_ID]			= LPCM_GENPD_SSWRP_DPU,
+	[CPM_LGA_SSWRP_EH_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_FABHBW_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_FABMED_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_FABRT_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_FABSTBY_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_FABSYSS_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_G2D_ID]			= LPCM_GENPD_SSWRP_G2D,
+	[CPM_LGA_SSWRP_GMC0_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GMC1_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GMC2_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GMC3_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GPCA_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GPCM_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GPDMA_ID]		= LPCM_GENPD_SSWRP_GPDMA,
+	[CPM_LGA_SSWRP_GPU_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GSA_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_GSW_ID]			= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_HSIO_N_ID]		= LPCM_GENPD_SSWRP_HSIO_N,
+	[CPM_LGA_SSWRP_HSIO_S_ID]		= LPCM_GENPD_SSWRP_HSIO_S,
+	[CPM_LGA_SSWRP_INF_TCU_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_ISPBE_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_ISPFE_ID]		= LPCM_GENPD_SSWRP_ISPFE,
+	[CPM_LGA_SSWRP_LSIO_E_ID]		= LPCM_GENPD_SSWRP_LSIO_E,
+	[CPM_LGA_SSWRP_LSIO_N_ID]		= LPCM_GENPD_SSWRP_LSIO_N,
+	[CPM_LGA_SSWRP_LSIO_S_ID]		= LPCM_GENPD_SSWRP_LSIO_S,
+	[CPM_LGA_SSWRP_MEMSS_ID]		= NOT_SUPPORTED,
+	[CPM_LGA_SSWRP_TPU_ID]			= NOT_SUPPORTED,
+};
+
+static const struct cpm_power_domain_desc lga_pm_domains[] = {
+	[LGA_PD_SSWRP_AOC] = {
+		.name = "sswrp-aoc-pd",
+		.sswrp_id = CPM_LGA_SSWRP_AOC_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.flags = GENPD_FLAG_ACTIVE_WAKEUP,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_AOC_CORE] = {
+		.name = "aoc-core-pd",
+		.sswrp_id = CPM_LGA_SSWRP_AOC_ID,
+		.subdomain_id = CPM_LGA_AOC_CORE_ID,
+		.on_at_init = true,
+		.parent_id = LGA_PD_SSWRP_AOC,
+	},
+	[LGA_PD_SSWRP_AURORA] = {
+		.name = "sswrp-aurora-pd",
+		.sswrp_id = CPM_LGA_SSWRP_AURORA_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_SSWRP_CODEC_3P] = {
+		.name = "sswrp-codec-3p-pd",
+		.sswrp_id = CPM_LGA_SSWRP_CODEC_3P_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_DPU_DP] = {
+		.name = "dpu-dp-pd",
+		.sswrp_id = CPM_LGA_SSWRP_DPU_ID,
+		.subdomain_id = CPM_LGA_DPU_DP_ID,
+		.parent_id = LGA_PD_SSWRP_DPU,
+	},
+	[LGA_PD_SSWRP_DPU] = {
+		.name = "sswrp-dpu-pd",
+		.sswrp_id = CPM_LGA_SSWRP_DPU_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_DPU_FE0] = {
+		.name = "dpu-fe0-pd",
+		.sswrp_id = CPM_LGA_SSWRP_DPU_ID,
+		.subdomain_id = CPM_LGA_DPU_FE0_ID,
+		.parent_id = LGA_PD_DPU_DSI0,
+	},
+	[LGA_PD_DPU_FE1] = {
+		.name = "dpu-fe1-pd",
+		.sswrp_id = CPM_LGA_SSWRP_DPU_ID,
+		.subdomain_id = CPM_LGA_DPU_FE1_ID,
+		.parent_id = LGA_PD_DPU_FE0,
+	},
+	[LGA_PD_DPU_DSI0] = {
+		.name = "dpu-dsi0-pd",
+		.sswrp_id = CPM_LGA_SSWRP_DPU_ID,
+		.subdomain_id = CPM_LGA_DPU_DSI0_ID,
+		.parent_id = LGA_PD_SSWRP_DPU,
+	},
+	[LGA_PD_DPU_DSI1] = {
+		.name = "dpu-dsi1-pd",
+		.sswrp_id = CPM_LGA_SSWRP_DPU_ID,
+		.subdomain_id = CPM_LGA_DPU_DSI1_ID,
+		.parent_id = LGA_PD_SSWRP_DPU,
+	},
+	[LGA_PD_SSWRP_EH] = {
+		.name = "sswrp-eh-pd",
+		.sswrp_id = CPM_LGA_SSWRP_EH_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_SSWRP_G2D] = {
+		.name = "sswrp-g2d-pd",
+		.sswrp_id = CPM_LGA_SSWRP_G2D_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_G2D_CORE] = {
+		.name = "g2d-core-pd",
+		.sswrp_id = CPM_LGA_SSWRP_G2D_ID,
+		.subdomain_id = CPM_LGA_G2D_CORE_ID,
+		.parent_id = LGA_PD_SSWRP_G2D,
+	},
+	[LGA_PD_SSWRP_GPDMA] = {
+		.name = "sswrp-gpdma-pd",
+		.sswrp_id = CPM_LGA_SSWRP_GPDMA_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_SSWRP_GPU] = {
+		.name = "sswrp-gpu-pd",
+		.sswrp_id = CPM_LGA_SSWRP_GPU_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_SSWRP_GSW] = {
+		.name = "sswrp-gsw-pd",
+		.sswrp_id = CPM_LGA_SSWRP_GSW_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_SSWRP_HSIO_N] = {
+		.name = "sswrp-hsio-n-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_HSIO_N_USB] = {
+		.name = "hsio-n-usb-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = CPM_LGA_HSIO_N_USB_ID,
+		.flags = GENPD_FLAG_ACTIVE_WAKEUP,
+		.on_at_init = true,
+		.parent_id = LGA_PD_SSWRP_HSIO_N,
+	},
+	[LGA_PD_HSIO_N_USB_PSW] = {
+		.name = "hsio-n-usb-psw-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = CPM_LGA_HSIO_N_USB_PSW_ID,
+		.flags = GENPD_FLAG_ACTIVE_WAKEUP,
+		.on_at_init = true,
+		.parent_id = LGA_PD_HSIO_N_USB,
+	},
+	[LGA_PD_HSIO_N_PCIE] = {
+		.name = "hsio-n-pcie-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = CPM_LGA_HSIO_N_PCIE_ID,
+		.flags = GENPD_FLAG_ACTIVE_WAKEUP,
+		.parent_id = LGA_PD_SSWRP_HSIO_N,
+	},
+	[LGA_PD_HSIO_N_DP] = {
+		.name = "hsio-n-dp-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = CPM_LGA_HSIO_N_DP_ID,
+		.parent_id = LGA_PD_HSIO_N_USB_PSW,
+	},
+	[LGA_PD_HSIO_N_EBU] = {
+		.name = "hsio-n-ebu-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = CPM_LGA_HSIO_N_EBU_ID,
+		.parent_id = LGA_PD_HSIO_N_USB,
+	},
+	[LGA_PD_HSIO_N_EBU_CORE] = {
+		.name = "hsio-n-ebu-core-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_N_ID,
+		.subdomain_id = CPM_LGA_HSIO_N_EBU_CORE_ID,
+		.parent_id = LGA_PD_HSIO_N_EBU,
+	},
+	[LGA_PD_SSWRP_HSIO_S] = {
+		.name = "sswrp-hsio-s-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_S_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.flags = GENPD_FLAG_RPM_ALWAYS_ON,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_HSIO_S_UFS_PREP] = {
+		.name = "hsio-s-ufs-prep-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_S_ID,
+		.subdomain_id = CPM_HSIO_S_UFS_PREP_ID,
+		.on_at_init = true,
+		.parent_id = LGA_PD_SSWRP_HSIO_S,
+	},
+	[LGA_PD_HSIO_S_UFS] = {
+		.name = "hsio-s-ufs-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_S_ID,
+		.subdomain_id = CPM_LGA_HSIO_S_UFS_ID,
+		.flags = GENPD_FLAG_RPM_ALWAYS_ON,
+		.on_at_init = true,
+		.parent_id = LGA_PD_HSIO_S_UFS_PREP,
+	},
+	[LGA_PD_HSIO_S_PCIE] = {
+		.name = "hsio-s-pcie-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_S_ID,
+		.subdomain_id = CPM_LGA_HSIO_S_PCIE_ID,
+		.parent_id = LGA_PD_SSWRP_HSIO_S,
+	},
+	[LGA_PD_HSIO_S_SD] = {
+		.name = "hsio-s-sd-pd",
+		.sswrp_id = CPM_LGA_SSWRP_HSIO_S_ID,
+		.subdomain_id = CPM_LGA_HSIO_S_SD_ID,
+		.parent_id = LGA_PD_HSIO_S_UFS,
+	},
+	[LGA_PD_SSWRP_ISPBE] = {
+		.name = "sswrp-ispbe-pd",
+		.sswrp_id = CPM_LGA_SSWRP_ISPBE_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_SSWRP_ISPFE] = {
+		.name = "sswrp-ispfe-pd",
+		.sswrp_id = CPM_LGA_SSWRP_ISPFE_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_ISPFE_PIPE] = {
+		.name = "ispfe-pipe-pd",
+		.sswrp_id = CPM_LGA_SSWRP_ISPFE_ID,
+		.subdomain_id = CPM_LGA_ISPFE_PIPE_ID,
+		.parent_id = LGA_PD_SSWRP_ISPFE,
+	},
+	[LGA_PD_ISPFE_CSIS] = {
+		.name = "ispfe-csis-pd",
+		.sswrp_id = CPM_LGA_SSWRP_ISPFE_ID,
+		.subdomain_id = CPM_LGA_ISPFE_CSIS_ID,
+		.parent_id = LGA_PD_SSWRP_ISPFE,
+	},
+	[LGA_PD_SSWRP_LSIO_E] = {
+		.name = "sswrp-lsio-e-pd",
+		.sswrp_id = CPM_LGA_SSWRP_LSIO_E_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.flags = GENPD_FLAG_RPM_ALWAYS_ON,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_LSIO_E_CLI_GPIO] = {
+		.name = "lsio-e-cli-gpio-pd",
+		.sswrp_id = CPM_LGA_SSWRP_LSIO_E_ID,
+		.subdomain_id = CPM_LGA_LSIO_CLI_GPIO_ID,
+		.flags = GENPD_FLAG_RPM_ALWAYS_ON,
+		.on_at_init = true,
+		.use_smc = true,
+		.parent_id = LGA_PD_SSWRP_LSIO_E,
+	},
+	[LGA_PD_SSWRP_LSIO_N] = {
+		.name = "sswrp-lsio-n-pd",
+		.sswrp_id = CPM_LGA_SSWRP_LSIO_N_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_LSIO_N_CLI_GPIO] = {
+		.name = "lsio-n-cli-gpio-pd",
+		.sswrp_id = CPM_LGA_SSWRP_LSIO_N_ID,
+		.subdomain_id = CPM_LGA_LSIO_CLI_GPIO_ID,
+		.on_at_init = true,
+		.parent_id = LGA_PD_SSWRP_LSIO_N,
+	},
+	[LGA_PD_SSWRP_LSIO_S] = {
+		.name = "sswrp-lsio-s-pd",
+		.sswrp_id = CPM_LGA_SSWRP_LSIO_S_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.on_at_init = true,
+		.parent_id = NOT_SUPPORTED,
+	},
+	[LGA_PD_LSIO_S_CLI_GPIO] = {
+		.name = "lsio-s-cli-gpio-pd",
+		.sswrp_id = CPM_LGA_SSWRP_LSIO_S_ID,
+		.subdomain_id = CPM_LGA_LSIO_CLI_GPIO_ID,
+		.on_at_init = true,
+		.parent_id = LGA_PD_SSWRP_LSIO_S,
+	},
+	[LGA_PD_SSWRP_TPU] = {
+		.name = "sswrp-tpu-pd",
+		.sswrp_id = CPM_LGA_SSWRP_TPU_ID,
+		.subdomain_id = NOT_SUPPORTED,
+		.flags = GENPD_FLAG_ACTIVE_WAKEUP,
+		.parent_id = NOT_SUPPORTED,
+	},
+};
+
+struct cpm_power_desc {
+	const int *sswrp_to_lpb_ids;
+	const int *sswrp_to_lpcm_ids;
+	const struct cpm_power_domain_desc *domains;
+	size_t num_domains;
+};
+
+static const struct cpm_power_desc lga_cpm_power_desc = {
+	.sswrp_to_lpb_ids = lga_sswrp_to_lpb,
+	.sswrp_to_lpcm_ids = lga_sswrp_to_lpcm,
+	.domains = lga_pm_domains,
+	.num_domains = ARRAY_SIZE(lga_pm_domains),
+};
+
+#endif /* _CPM_POWER_LGA_H */
diff --git a/drivers/pmdomain/google/cpm-power-trace.h b/drivers/pmdomain/google/cpm-power-trace.h
new file mode 100644
index 000000000000..14c4c3c8e7d8
--- /dev/null
+++ b/drivers/pmdomain/google/cpm-power-trace.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM cpm_power
+
+#if !defined(_TRACE_CPM_POWER_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_CPM_POWER_TRACE_H
+
+#include <linux/tracepoint.h>
+
+DECLARE_EVENT_CLASS(cpm_power_send_mail_events,
+	TP_PROTO(struct cpm_power_domain *pd, bool on),
+	TP_ARGS(pd, on),
+	TP_STRUCT__entry(
+		__string(name, pd->name)
+		__field(unsigned char, sswrp_id)
+		__field(unsigned char, subdomain_id)
+		__field(bool, on)
+	),
+	TP_fast_assign(
+		__assign_str(name);
+		__entry->sswrp_id = pd->sswrp_id;
+		__entry->subdomain_id = pd->cpm_lpcm_subdomain_id;
+		__entry->on = on;
+	),
+	TP_printk("\t%#04x.%02x:%s\ten:%d",
+		  __entry->sswrp_id, __entry->subdomain_id,
+		  __get_str(name), __entry->on)
+);
+DEFINE_EVENT(cpm_power_send_mail_events, send_mail_lpb,
+	TP_PROTO(struct cpm_power_domain *pd, bool on),
+	TP_ARGS(pd, on)
+);
+DEFINE_EVENT(cpm_power_send_mail_events, send_mail_lpcm,
+	TP_PROTO(struct cpm_power_domain *pd, bool on),
+	TP_ARGS(pd, on)
+);
+
+DECLARE_EVENT_CLASS(cpm_power_mail_result_events,
+	TP_PROTO(struct cpm_power_domain *pd, bool on, int result),
+	TP_ARGS(pd, on, result),
+	TP_STRUCT__entry(
+		__string(name, pd->name)
+		__field(unsigned char, sswrp_id)
+		__field(unsigned char, subdomain_id)
+		__field(bool, on)
+		__field(int, result)
+	),
+	TP_fast_assign(
+		__assign_str(name);
+		__entry->sswrp_id = pd->sswrp_id;
+		__entry->subdomain_id = pd->cpm_lpcm_subdomain_id;
+		__entry->on = on;
+		__entry->result = result;
+	),
+	TP_printk("\t%#04x.%02x:%s\ten:%d (res:%d)",
+		  __entry->sswrp_id, __entry->subdomain_id,
+		  __get_str(name), __entry->on, __entry->result)
+);
+DEFINE_EVENT(cpm_power_mail_result_events, recv_result_lpb,
+	TP_PROTO(struct cpm_power_domain *pd, bool on, int result),
+	TP_ARGS(pd, on, result)
+);
+DEFINE_EVENT(cpm_power_mail_result_events, recv_result_lpcm,
+	TP_PROTO(struct cpm_power_domain *pd, bool on, int result),
+	TP_ARGS(pd, on, result)
+);
+
+#endif /* _TRACE_CPM_POWER_TRACE_H */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH ../drivers/pmdomain/google
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE cpm-power-trace
+
+#include <trace/define_trace.h>
diff --git a/drivers/pmdomain/google/cpm-power.c b/drivers/pmdomain/google/cpm-power.c
new file mode 100644
index 000000000000..6dc7a61e30ab
--- /dev/null
+++ b/drivers/pmdomain/google/cpm-power.c
@@ -0,0 +1,637 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+
+#include <dt-bindings/power/google,lga-cpm.h>
+#include <linux/arm-smccc.h>
+#include <linux/atomic.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/debugfs.h>
+#include <linux/dev_printk.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_domain.h>
+#include <soc/google/goog_cpm_service_ids.h>
+#include <soc/google/google-cpm-mba.h>
+
+#include "lpb_service.h"
+
+enum power_state {
+	PD_STATE_OFF = 0,
+	PD_STATE_ON = 1,
+	PD_STATE_COUNT,
+};
+
+struct cpm_power_controller;
+
+struct cpm_power_domain {
+	struct cpm_power_controller *controller;
+	struct generic_pm_domain genpd;
+	const char *name;
+	bool is_top_pd;
+	bool use_smc;
+	int id;
+	int sswrp_id;
+	int subdomain_id;
+	int parent_id;
+	atomic_t state;
+	struct completion cpm_resp_done;
+	/*
+	 * SSWRP ID that CPM uses internally has a different value depending on
+	 * |is_top_pd|:
+	 * - for top power domain, it is cpm_lpb_sswrp_id.
+	 * - for sub power domain, it is cpm_lpcm_sswrp_id.
+	 */
+	union {
+		int cpm_lpcm_sswrp_id;
+		int cpm_lpb_sswrp_id;
+	};
+	int cpm_lpcm_subdomain_id;
+};
+
+struct cpm_power_controller {
+	struct device *dev;
+	struct cpm_client *client;
+	struct dentry *debugfs_root;
+	struct cpm_power_domain *pds;
+	u32 pd_count;
+	struct genpd_onecell_data pd_data;
+};
+
+#define CREATE_TRACE_POINTS
+#include "cpm-power-trace.h"
+
+#include "cpm-power-lga.h"
+
+#define LPCM_CMD_SET_GEN_PD	1
+#define POWER_ON_BITFIELD	GENMASK(31, 24)
+#define PD_ID_BITFIELD		GENMASK(23, 16)
+#define SSWRP_ID_BITFIELD	GENMASK(15, 8)
+#define REQ_ID_BITFIELD		GENMASK(7, 0)
+
+#define NO_ERROR		0
+#define LPB_STATUS_ON		3
+#define LPB_STATUS_OFF		0
+#define LPB_EVENT_ON		0
+#define LPB_EVENT_OFF		1
+#define CPM_COMMON_PROT_NO_ERR	15
+
+#define SIP_SVC_PD_CONTROL	0x82000701
+
+#define ON_OFF_STR(x)		((x) ? "power_on" : "power_off")
+
+#define CPM_MBX_SEND_TIMEOUT_MS	3000
+#define CPM_MBX_RECV_TIMEOUT_MS	3000
+
+static inline struct cpm_power_domain *to_power_domain(struct generic_pm_domain *d)
+{
+	return container_of(d, struct cpm_power_domain, genpd);
+}
+
+/*
+ * This callback is triggered when CPM signals the transition has finished.
+ */
+static void cpm_power_rx_callback(u32 context, const struct cpm_payload *msg, void *priv)
+{
+	struct cpm_power_controller *pc = priv;
+	struct device *dev = pc->dev;
+	struct cpm_power_domain *pd = NULL;
+	u32 resource_sid = msg->payload[0];
+	int i;
+
+	dev_dbg(dev, "rx callback msg %d %d %d\n", msg->payload[0],
+		msg->payload[1], msg->payload[2]);
+
+	for (i = 0; i < pc->pd_count; i++) {
+		/* For MBA service */
+		if (pc->pds[i].is_top_pd &&
+		    pc->pds[i].cpm_lpb_sswrp_id == resource_sid) {
+			pd = &pc->pds[i];
+			break;
+		}
+	}
+	if (!pd) {
+		dev_err(dev,
+			"cannot find corresponding power domain for ID: %d\n",
+			resource_sid);
+		return;
+	}
+
+	dev_dbg(dev, "%s: power transition completed\n", pd->name);
+	complete(&pd->cpm_resp_done);
+}
+
+static int send_lpb_request(struct cpm_power_domain *pd, enum power_state state)
+{
+	struct device *dev = pd->controller->dev;
+	struct cpm_payload resp_msg;
+	struct cpm_payload req_msg = {};
+	struct cpm_request cpm_req = {
+		.msg_type = REQUEST_MSG,
+		.req_msg = &req_msg,
+		.resp_msg = &resp_msg,
+		.dst_id = CPM_COMMON_LPB_SERVICE,
+		.tout_ms = CPM_MBX_SEND_TIMEOUT_MS,
+	};
+	int ret;
+
+	dev_dbg(dev, "%s: %s: sswrp_id = %d\n", pd->name, ON_OFF_STR(state),
+		pd->cpm_lpb_sswrp_id);
+
+	req_msg.payload[0] = LPB_CMD_SSWRP_STATE_SET;
+	req_msg.payload[1] = pd->cpm_lpb_sswrp_id;
+	req_msg.payload[2] = state;
+
+	reinit_completion(&pd->cpm_resp_done);
+
+	trace_send_mail_lpb(pd, state);
+	ret = cpm_send_message(pd->controller->client, &cpm_req);
+
+	if (ret < 0) {
+		dev_err(dev, "%s: %s: send lpb message failed ret (%d)\n",
+			pd->name, ON_OFF_STR(state), ret);
+
+		return ret;
+	}
+
+	dev_dbg(dev, "%s: %s: lpb response msg %d %d %d\n", pd->name,
+		ON_OFF_STR(state), resp_msg.payload[0], resp_msg.payload[1],
+		resp_msg.payload[2]);
+
+	if (resp_msg.payload[0] != CPM_COMMON_PROT_NO_ERR) {
+		/* We failed completely no additional message is waiting */
+		dev_err(dev, "%s: %s: CPM LPB service failed with err=%d\n",
+			pd->name, ON_OFF_STR(state), resp_msg.payload[0]);
+
+		return -EPROTO;
+	}
+
+	switch (resp_msg.payload[1]) {
+	case LPB_SERVICE_CMD_SUCCESS:
+		/* Command requires no asynchronous processing -- we are done */
+		ret = 0;
+		break;
+	case LPB_SERVICE_PWRUP_STARTED:
+	case LPB_SERVICE_PWRDN_STARTED:
+		/*
+		 * We have received the first response for power on/off.  The
+		 * final result will be received asynchronously.
+		 */
+
+		ret = wait_for_completion_timeout(&pd->cpm_resp_done,
+						  msecs_to_jiffies(CPM_MBX_RECV_TIMEOUT_MS));
+		if (ret == 0) {
+			dev_err(dev, "%s: %s: wait for CPM response timeout\n",
+				pd->name, ON_OFF_STR(state));
+			ret = -ETIMEDOUT;
+			break;
+		}
+		ret = 0;
+		break;
+	case LPB_SERVICE_CMD_FAIL:
+	default:
+		/* Command got through but the result was failure */
+		dev_err(dev, "%s: %s: CPM LPB services returned fail, err=%d\n",
+			pd->name, ON_OFF_STR(state), resp_msg.payload[1]);
+		ret = -EPROTO;
+		break;
+	}
+
+	trace_recv_result_lpb(pd, state, ret);
+
+	return ret;
+}
+
+static int request_lpb_vote_status(struct generic_pm_domain *genpd)
+{
+	struct cpm_power_domain *pd = to_power_domain(genpd);
+	struct device *dev = pd->controller->dev;
+	struct cpm_payload resp_msg;
+	struct cpm_payload req_msg = {};
+	struct cpm_request cpm_req = {
+		.msg_type = REQUEST_MSG,
+		.req_msg = &req_msg,
+		.resp_msg = &resp_msg,
+		.dst_id = CPM_COMMON_LPB_SERVICE,
+		.tout_ms = CPM_MBX_SEND_TIMEOUT_MS,
+	};
+	int ret;
+
+	if (!pd->is_top_pd) {
+		dev_warn(dev, "%s: Only SSWRP vote status is supported\n",
+			 pd->name);
+		return -EINVAL;
+	}
+
+	req_msg.payload[0] = LPB_CMD_SSWRP_STATE_GET;
+	req_msg.payload[1] = pd->cpm_lpb_sswrp_id;
+
+	ret = cpm_send_message(pd->controller->client, &cpm_req);
+	if (ret < 0) {
+		dev_err(dev, "%s: failed to get SSWRP state, MBA err: %d\n",
+			pd->name, ret);
+		return ret;
+	}
+
+	return resp_msg.payload[1] == LPB_STATUS_ON ? PD_STATE_ON :
+						      PD_STATE_OFF;
+}
+
+static int send_lpcm_request(struct cpm_power_domain *pd, enum power_state state)
+{
+	struct device *dev = pd->controller->dev;
+	struct cpm_payload resp_msg;
+	struct cpm_payload req_msg = {};
+	struct cpm_request cpm_req = {
+		.msg_type = REQUEST_MSG,
+		.req_msg = &req_msg,
+		.resp_msg = &resp_msg,
+		.dst_id = CPM_COMMON_LPCM_SERVICE,
+		.tout_ms = CPM_MBX_SEND_TIMEOUT_MS,
+	};
+	int ret;
+
+	if (pd->cpm_lpcm_sswrp_id == NOT_SUPPORTED) {
+		dev_err(dev,
+			"%s: %s: ID %d is not supported by LPCM service\n",
+			pd->name, ON_OFF_STR(state), pd->sswrp_id);
+		return -EPROTONOSUPPORT;
+	}
+
+	dev_dbg(dev, "%s: %s: lpcm_id = %d\n", pd->name, ON_OFF_STR(state),
+		pd->cpm_lpcm_sswrp_id);
+	req_msg.payload[0] =
+		FIELD_PREP(REQ_ID_BITFIELD, LPCM_CMD_SET_GEN_PD) |
+		FIELD_PREP(SSWRP_ID_BITFIELD, pd->cpm_lpcm_sswrp_id) |
+		FIELD_PREP(PD_ID_BITFIELD, pd->cpm_lpcm_subdomain_id) |
+		FIELD_PREP(POWER_ON_BITFIELD, state);
+
+	trace_send_mail_lpcm(pd, state);
+	ret = cpm_send_message(pd->controller->client, &cpm_req);
+	trace_recv_result_lpcm(pd, state, resp_msg.payload[0]);
+
+	if (ret < 0) {
+		dev_err(dev, "%s: %s: send lpcm message failed ret (%d)\n",
+			pd->name, ON_OFF_STR(state), ret);
+
+		return ret;
+	}
+
+	dev_dbg(dev, "%s: %s: got resp from %u, data %u %u %u\n", pd->name,
+		ON_OFF_STR(state), cpm_req.dst_id, resp_msg.payload[0],
+		resp_msg.payload[1], resp_msg.payload[2]);
+
+	switch (resp_msg.payload[0]) {
+	case NO_ERROR:
+		dev_dbg(dev, "%s: %s: lpcm service success\n", pd->name,
+			ON_OFF_STR(state));
+		ret = 0;
+		break;
+	default:
+		dev_err(dev, "%s: %s: unknown error code %d\n", pd->name,
+			ON_OFF_STR(state), resp_msg.payload[0]);
+		ret = -EPROTO;
+		break;
+	}
+
+	return ret;
+}
+
+static inline int lpcm_smc(struct cpm_power_domain *pd, enum power_state state)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(SIP_SVC_PD_CONTROL, pd->sswrp_id,
+		      pd->cpm_lpcm_subdomain_id, state, 0, 0, 0, 0, &res);
+
+	WARN_ON(res.a0);
+	if ((unsigned long)res.a1) {
+		dev_err(pd->controller->dev, "Failed SMC call sswrp:%d sub-domain:%d, res.a1:%lu\n",
+			pd->sswrp_id, pd->cpm_lpcm_subdomain_id, res.a1);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cpm_set_power_state(struct generic_pm_domain *domain, enum power_state state)
+{
+	int ret;
+	struct cpm_power_domain *pd = to_power_domain(domain);
+
+	if (state >= PD_STATE_COUNT || state < 0)
+		return -EINVAL;
+
+	if (pd->use_smc)
+		ret = lpcm_smc(pd, state);
+	else if (pd->is_top_pd)
+		ret = send_lpb_request(pd, state);
+	else
+		ret = send_lpcm_request(pd, state);
+
+	if (!ret)
+		atomic_set(&pd->state, state);
+
+	return ret;
+}
+
+static int cpm_power_on(struct generic_pm_domain *domain)
+{
+	return cpm_set_power_state(domain, PD_STATE_ON);
+}
+
+static int cpm_power_off(struct generic_pm_domain *domain)
+{
+	return cpm_set_power_state(domain, PD_STATE_OFF);
+}
+
+static int cpm_power_apply_desc(struct cpm_power_domain *pd,
+				const struct cpm_power_domain_desc *domain)
+{
+	struct device *dev = pd->controller->dev;
+
+	if (domain->on_at_init)
+		atomic_set(&pd->state, PD_STATE_ON);
+
+	pd->use_smc = domain->use_smc;
+	pd->genpd.flags |= domain->flags;
+
+	/* If we want it "always on", it must already be on. */
+	if (pd->genpd.flags & GENPD_FLAG_ALWAYS_ON) {
+		if (atomic_read(&pd->state) == PD_STATE_OFF) {
+			dev_err(dev, "%s: always-on PM domain is not on\n",
+				pd->name);
+			return -EINVAL;
+		}
+	}
+
+	/* If we want it "RPM always on", it must already be on. */
+	if (pd->genpd.flags & GENPD_FLAG_RPM_ALWAYS_ON) {
+		if (atomic_read(&pd->state) == PD_STATE_OFF) {
+			dev_err(dev, "%s: rpm-always-on PM domain is not on\n",
+				pd->name);
+			return -EINVAL;
+		}
+	}
+
+	if (pd->genpd.flags & GENPD_FLAG_IRQ_SAFE) {
+		if (pd->genpd.flags & GENPD_FLAG_RPM_ALWAYS_ON) {
+			dev_err(dev, "%s: rpm-always-on and irq-safe both set\n",
+				pd->name);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static struct cpm_power_domain *cpm_find_pd_by_id(struct cpm_power_controller *pc, int id)
+{
+	if (id >= 0 && id < pc->pd_count)
+		return &pc->pds[id];
+	return NULL;
+}
+
+static void cpm_genpd_remove_action(void *data)
+{
+	struct generic_pm_domain *genpd = data;
+
+	pm_genpd_remove(genpd);
+}
+
+static void cpm_genpd_remove_subdomain_action(void *data)
+{
+	struct cpm_power_domain *pd = data;
+	struct cpm_power_domain *parent_pd = cpm_find_pd_by_id(pd->controller, pd->parent_id);
+
+	pm_genpd_remove_subdomain(&parent_pd->genpd, &pd->genpd);
+}
+
+/*
+ * This flag can be dangerous because it allows power on/off a power domain
+ * without changing reference count in genpd core. This can lead to an
+ * inconsistent state where genpd core thinks the domain is ON while it is
+ * actually OFF and vice versa. Only define this flag for testing purposes.
+ */
+static int cpm_power_debugfs_state_set(void *domain, u64 val)
+{
+	struct generic_pm_domain *genpd = domain;
+	int ret = 0;
+
+	dev_dbg(&genpd->dev, "%s\n", ON_OFF_STR(val));
+
+	mutex_lock(&genpd->mlock);
+	if (val)
+		ret = cpm_power_on(genpd);
+	else
+		ret = cpm_power_off(genpd);
+	mutex_unlock(&genpd->mlock);
+
+	return ret;
+}
+
+static int cpm_power_debugfs_state_get(void *domain, u64 *val)
+{
+	struct generic_pm_domain *genpd = domain;
+	int ret;
+
+	ret = request_lpb_vote_status(genpd);
+	if (ret < 0)
+		return ret;
+
+	*val = ret;
+
+	return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(cpm_power_debugfs_state_fops,
+			 cpm_power_debugfs_state_get,
+			 cpm_power_debugfs_state_set, "%llu\n");
+
+static void cpm_power_debugfs_init(struct cpm_power_controller *controller)
+{
+	controller->debugfs_root = debugfs_create_dir("cpm-power", NULL);
+
+	for (int i = 0; i < controller->pd_count; ++i) {
+		struct cpm_power_domain *pd = &controller->pds[i];
+		struct dentry *d =
+			debugfs_create_dir(pd->genpd.name,
+					   controller->debugfs_root);
+
+		debugfs_create_file("state", 0660, d, &pd->genpd,
+				    &cpm_power_debugfs_state_fops);
+	}
+}
+
+static void cpm_power_debugfs_remove(struct cpm_power_controller *controller)
+{
+	debugfs_remove_recursive(controller->debugfs_root);
+}
+
+static int cpm_power_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+	struct device *dev = &auxdev->dev;
+	struct cpm_power_controller *controller;
+	const struct cpm_power_desc *desc;
+	int ret, i, pd_count;
+
+	controller = devm_kzalloc(dev, sizeof(*controller), GFP_KERNEL);
+	if (!controller)
+		return -ENOMEM;
+
+	controller->dev = dev;
+
+	auxiliary_set_drvdata(auxdev, controller);
+
+	desc = (const struct cpm_power_desc *)id->driver_data;
+	if (!desc)
+		return -EINVAL;
+
+	pd_count = desc->num_domains;
+	controller->pds = devm_kcalloc(dev, pd_count, sizeof(*controller->pds), GFP_KERNEL);
+	if (!controller->pds)
+		return -ENOMEM;
+	controller->pd_count = pd_count;
+
+	controller->pd_data.domains =
+		devm_kcalloc(dev, pd_count, sizeof(*controller->pd_data.domains),
+			     GFP_KERNEL);
+	if (!controller->pd_data.domains)
+		return -ENOMEM;
+	controller->pd_data.num_domains = pd_count;
+
+	for (i = 0; i < pd_count; i++) {
+		const struct cpm_power_domain_desc *domain = &desc->domains[i];
+		struct cpm_power_domain *pd = &controller->pds[i];
+
+		pd->sswrp_id = domain->sswrp_id;
+		pd->is_top_pd = (domain->subdomain_id == NOT_SUPPORTED);
+		atomic_set(&pd->state, PD_STATE_OFF);
+		pd->name = domain->name;
+		pd->controller = controller;
+		pd->genpd.name = domain->name;
+		pd->genpd.power_on = cpm_power_on;
+		pd->genpd.power_off = cpm_power_off;
+		pd->id = i;
+		pd->subdomain_id = domain->subdomain_id;
+		pd->parent_id = domain->parent_id;
+
+		pd->cpm_lpcm_subdomain_id = domain->subdomain_id;
+
+		if (pd->is_top_pd && desc->sswrp_to_lpb_ids)
+			pd->cpm_lpb_sswrp_id =
+				desc->sswrp_to_lpb_ids[pd->sswrp_id];
+		else if (desc->sswrp_to_lpcm_ids)
+			pd->cpm_lpcm_sswrp_id =
+				desc->sswrp_to_lpcm_ids[pd->sswrp_id];
+		else
+			return -EINVAL;
+
+		init_completion(&pd->cpm_resp_done);
+
+		ret = cpm_power_apply_desc(pd, domain);
+		if (ret) {
+			dev_err(dev, "%s: failed to parse attributes\n",
+				pd->name);
+			return ret;
+		}
+	}
+
+	controller->client =
+		devm_cpm_request_client(dev,
+					APC_COMMON_SERVICE_ID_POWER_CONTROLLER,
+					cpm_power_rx_callback,
+					controller);
+	if (IS_ERR(controller->client))
+		return dev_err_probe(dev, PTR_ERR(controller->client),
+				     "failed to request mailbox channel\n");
+
+	/* First, register all domains. */
+	for (i = 0; i < pd_count; i++) {
+		struct cpm_power_domain *pd = &controller->pds[i];
+
+		ret = pm_genpd_init(&pd->genpd, NULL,
+				    atomic_read(&pd->state) == PD_STATE_OFF);
+		if (ret) {
+			dev_err(dev, "%s: failed to init power domain\n",
+				pd->name);
+			return ret;
+		}
+
+		ret = devm_add_action_or_reset(dev, cpm_genpd_remove_action, &pd->genpd);
+		if (ret)
+			return ret;
+
+		controller->pd_data.domains[i] = &pd->genpd;
+	}
+
+	/* Next, link subdomains to their parents. */
+	for (i = 0; i < pd_count; i++) {
+		struct cpm_power_domain *pd = &controller->pds[i];
+		struct cpm_power_domain *parent_pd;
+
+		if (pd->parent_id == NOT_SUPPORTED)
+			continue;
+
+		parent_pd = cpm_find_pd_by_id(controller, pd->parent_id);
+		if (!parent_pd) {
+			dev_err(dev, "failed to find parent for %s\n", pd->name);
+			return -EINVAL;
+		}
+		ret = pm_genpd_add_subdomain(&parent_pd->genpd, &pd->genpd);
+		if (ret) {
+			dev_err(dev, "failed to add subdomain %s to %s: %d\n",
+				pd->name, parent_pd->name, ret);
+			return ret;
+		}
+		ret = devm_add_action_or_reset(dev, cpm_genpd_remove_subdomain_action, pd);
+		if (ret)
+			return ret;
+	}
+
+	ret = of_genpd_add_provider_onecell(dev->of_node, &controller->pd_data);
+	if (ret) {
+		dev_err(dev, "failed to add genpd provider: %d\n", ret);
+		return ret;
+	}
+
+	cpm_power_debugfs_init(controller);
+
+	return 0;
+}
+
+static void cpm_power_remove(struct auxiliary_device *auxdev)
+{
+	struct cpm_power_controller *controller = auxiliary_get_drvdata(auxdev);
+	struct device *dev = &auxdev->dev;
+	struct device_node *np = dev->of_node;
+
+	cpm_power_debugfs_remove(controller);
+
+	of_genpd_del_provider(np);
+}
+
+static const struct auxiliary_device_id cpm_power_id_table[] = {
+	{
+		.name = "google_cpm_mba.power-controller",
+		.driver_data = (kernel_ulong_t)&lga_cpm_power_desc,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, cpm_power_id_table);
+
+static struct auxiliary_driver cpm_power_driver = {
+	.probe = cpm_power_probe,
+	.remove = cpm_power_remove,
+	.name = "cpm-power-controller",
+	.id_table = cpm_power_id_table,
+};
+
+module_auxiliary_driver(cpm_power_driver);
+
+MODULE_DESCRIPTION("Google Tensor CPM power controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pmdomain/google/lpb_service.h b/drivers/pmdomain/google/lpb_service.h
new file mode 100644
index 000000000000..4b84e2b8a5a8
--- /dev/null
+++ b/drivers/pmdomain/google/lpb_service.h
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+#ifndef _GOOGLE_LPB_SERVICE_H
+#define _GOOGLE_LPB_SERVICE_H
+
+/* Contains definitions needed to use the LPB mailbox service */
+
+/*
+ * SERVICE_ID: CPM_COMMON_LPB_SERVICE
+ *
+ * Disabled for production
+ * CLI to test LPB
+ *
+ * CMD: LPB_CMD_DEBUG_SET, LPB_CMD_SSWRP_STATE_SET
+ * Queue Mode:
+ * Word 0: Queue mode header
+ * Word 1: Cmd: LPB_CMD_DEBUG_SET | LPB_CMD_SSWRP_STATE_SET
+ *  bit [ 31 - 0 ]
+ *      | cmd    |
+ * Word 2: LPB SERVICE SSWRP ID
+ *  bit [ 31 - 0 ]
+ *      | SERVICE SSWRP ID |
+ * Word 3: Set on or off (1 is on, anything else is off)
+ *  bit [ 31 - 0 ]
+ *      | state  |
+ *
+ * Response:
+ * Word 0: Queue mode header
+ * Word 1: CPM_COMMON_PROT_NO_ERR on success, else error
+ *  bit [ 31 - 0                 ]
+ *      | cpm_common_mba_error_t |
+ * Word 2:
+ *  bit [ 31 - 0                   ]
+ *      | lpb_service_cmd_status_t |
+ * Word 3: LPB Service ID that was changed
+ *  bit [ 31 - 0           ]
+ *      | Service SSWRP ID |
+ *
+ * Polling Mode:
+ * Word 0: Header and data (1 to set on, 0 to set off)
+ *         cmd: LPB_CMD_DEBUG_SET | LPB_CMD_SSWRP_STATE_SET
+ *  bit [ 31 - 16             | 15-0 ]
+ *      | polling mode header | cmd  |
+ * Word 1: LPB SERVICE SSWRP ID
+ *  bit [ 31 - 0 ]
+ *      | SERVICE SSWRP ID |
+ * Word 2: state (1 on, 0 off)
+ *  bit [ 31 - 0 ]
+ *      | state  |
+ *
+ * Response:
+ * Word 0: Header
+ *  bit [ 31 - 16             | 15 - 0 ]
+ *      | polling mode header | unused |
+ * Word 1: CPM_COMMON_PROT_NO_ERR on success, else error
+ *  bit [ 31 - 0                 ]
+ *      | cpm_common_mba_error_t |
+ * Word 2:
+ *  bit [ 31 - 0                   ]
+ *      | lpb_service_cmd_status_t |
+ * Word 3: LPB Service ID that was changed
+ *  bit [ 31 - 0           ]
+ *      | Service SSWRP ID |
+ *
+ * CMD: LPB_CMD_DEBUG_SET, LPB_CMD_SSWRP_STATE_SET
+ *
+ * CMD: LPB_CMD_SSWRP_STATE_GET
+ * Queue Mode: Not supported at this time
+ * Polling Mode:
+ * Word 0: Header and data (1 to set on, 0 to set off)
+ *         cmd: LPB_CMD_SSWRP_STATE_GET
+ *  bit [ 31 - 16             | 15-0 ]
+ *      | polling mode header | cmd  |
+ * Word 1: LPB SERVICE SSWRP ID
+ *  bit [ 31 - 0           ]
+ *      | SERVICE SSWRP ID |
+ *
+ * Response:
+ * Word 0: Header
+ *  bit [ 31 - 16             | 15 - 0 ]
+ *      | polling mode header | unused |
+ * Word 1: lpb_service_state_t (LPB_SERVICE_STATE_ON | LPB_SERVICE_STATE_OFF)
+ *  bit [ 31 - 0              ]
+ *      | lpb_service_state_t |
+ *
+ * CMD: LPB_CMD_SSWRP_STATE_GET
+ *
+ * CMD: LPB_CMD_REQ_DRAM_STATE
+ * Polling Mode:
+ * Word 0: Header and data (1 to set on, 0 to set off)
+ *         cmd: LPB_CMD_REQ_DRAM_STATE
+ *  bit [ 31 - 16             | 15-0 ]
+ *      | polling mode header | cmd  |
+ * Word 1: state (1 on, all else off)
+ *  bit [ 31 - 0 ]
+ *      | state  |
+ *
+ * Response:
+ * Word 0: Header
+ *  bit [ 31 - 16             | 15 - 0 ]
+ *      | polling mode header | unused |
+ * Word 1: Error code (CPM_COMMON_PROT_NO_ERR for success, else an error code)
+ *  bit [ 31 - 0     ]
+ *      | error code |
+ *
+ * CMD: LPB_CMD_REQ_DRAM_STATE
+ *
+ * CMD: LPB_CMD_GET_DRAM_STATE
+ * Polling Mode:
+ * Word 0: Header and data (1 to set on, 0 to set off)
+ *         cmd: LPB_CMD_GET_DRAM_STATE
+ *  bit [ 31 - 16             | 15-0 ]
+ *      | polling mode header | cmd  |
+ *
+ * Response:
+ * Word 0: Header
+ *  bit [ 31 - 16             | 15 - 0 ]
+ *      | polling mode header | unused |
+ * Word 1: 0 for OFF, 1 for ON, else CPM_COMMON_PROT_GENERIC_ERR
+ *  bit [ 31 - 0     ]
+ *      | state |
+ *
+ * CMD: LPB_CMD_GET_DRAM_STATE
+ *
+ * SERVICE_ID: CPM_COMMON_LPB_SERVICE
+ */
+
+/*
+ * Add all LPB specific definitions here.
+ * Note: CPM, BMSM, and GDMC are not on this list, as they don't have an LPB
+ * sequencer
+ */
+
+/* LGA */
+enum lpb_service_sswrp_id {
+	LPB_SERVICE_AOC_ID,
+	LPB_SERVICE_AURORA_ID,
+	LPB_SERVICE_CODEC_3P_ID,
+	LPB_SERVICE_CPU_ID,
+	LPB_SERVICE_CPUACC_ID,
+	LPB_SERVICE_DPU_ID,
+	LPB_SERVICE_EH_ID,
+	LPB_SERVICE_FABHBW_ID,
+	LPB_SERVICE_FABMED_ID,
+	LPB_SERVICE_FABRT_ID,
+	LPB_SERVICE_FABSTBY_ID,
+	LPB_SERVICE_FABSYSS_ID,
+	LPB_SERVICE_G2D_ID,
+	LPB_SERVICE_GMC0_ID,
+	LPB_SERVICE_GMC1_ID,
+	LPB_SERVICE_GMC2_ID,
+	LPB_SERVICE_GMC3_ID,
+	LPB_SERVICE_GPCA_ID,
+	LPB_SERVICE_GPCM_ID,
+	LPB_SERVICE_GPDMA_ID,
+	LPB_SERVICE_GPU_ID,
+	LPB_SERVICE_GSA_ID,
+	LPB_SERVICE_GSW_ID,
+	LPB_SERVICE_HSIO_N_ID,
+	LPB_SERVICE_HSIO_S_ID,
+	LPB_SERVICE_INF_TCU_ID,
+	LPB_SERVICE_ISPBE_ID,
+	LPB_SERVICE_ISPFE_ID,
+	LPB_SERVICE_LSIO_E_ID,
+	LPB_SERVICE_LSIO_N_ID,
+	LPB_SERVICE_LSIO_S_ID,
+	LPB_SERVICE_MEMSS_ID,
+	LPB_SERVICE_TPU_ID,
+	LPB_SERVICE_NUM_SSWRPS,
+};
+
+/*
+ * Used to indicate what has just happened as a result of the below commands.
+ * Can be a generic success/error, or something more meaningful.
+ */
+
+enum lpb_service_cmd_status {
+	LPB_SERVICE_CMD_SUCCESS = 0xf,
+	LPB_SERVICE_CMD_FAIL,
+	LPB_SERVICE_PWRDN_STARTED,
+	LPB_SERVICE_PWRUP_STARTED,
+	LPB_SERVICE_NO_EFFECT,
+};
+
+enum lpb_service_state {
+	LPB_SERVICE_STATE_OFF = 0x1,
+	LPB_SERVICE_STATE_ON,
+};
+
+#define LPB_SVC_MSG_SIZE			(4)
+#define LPB_CMD_DEBUG_SET			(1)
+#define LPB_CMD_SSWRP_STATE_SET			(2)
+#define LPB_CMD_SSWRP_STATE_GET			(3)
+#define LPB_CMD_REQUEST_COMPUTE_SSWRP_OFF	(4)
+#define LPB_CMD_REQ_DRAM_STATE			(5)
+#define LPB_CMD_GET_DRAM_STATE			(6)
+
+#endif /* _GOOGLE_LPB_SERVICE_H */
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 5/9] clk: google: Add CPM clock driver
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (3 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 4/9] pmdomain: google: Add CPM power-domain driver Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-26 21:43 ` [PATCH 6/9] reset: google: Add CPM reset driver Brian Norris
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

Google Tensor SoCs (used on Pixel phones) manage many resources
(including most clocks) using a Central Power Manager (CPM)
co-processor. We communicate with CPM using mailboxes, abstracted in the
cpm_request_client(), etc., API.

The clock tree is largely not represented fully by this driver, because
CPM hides most of the divider, mux, and parent details, instead
maintaining its own internal table of mappings for divider and mux
values. The one exception is for some of the DPU clocks, where several
clocks and dividers are exposed directly, and the firmware API works on
divider values instead of clock rates. I attempt to represent this
properly with dividers and parents, although I don't have the full
display stack running to verify everything yet.

A few clocks are not managed by CPM, but instead utilize an SMC call for
lower latency. We still represent those within this driver, as they are
part of the same clock hierarchy.

For posterity, I list a summary of changes made relative to the
downstream Pixel driver:

 * CPM clients use the auxiliary_bus, instead of individual platform
   devices (and DT nodes) with phandles to a pseudo-mailbox

 * Instead of device nodes for each clock (and therefore a provider for
   each), we use a per-SoC table of clocks, register them all, and have
   a single clock-cells==2 provider

 * Use devm more heavily

 * Drop ACG (auto-clock gating) and QCH (Q-Channel Mode) custom APIs;
   these are mostly not expected to be used. We only support the
   CLK_GATE feature.

 * Add device objects for clocks that need a power domain, and attach
   per-LPCM power domains to those clock devices. The downstream driver
   had additional device tree properties for "power domains to turn on
   during probe", relying on consumers to manage these domains
   post-probe. I chose instead to simply attach these domains to the
   clocks themselves.

 * Represent hsion-dp0-clk, hsion-dp1-clk, dpu-dp0-pix-clk, and
   dpu-dp1-pix-clk with dividers and parents. In Pixel drivers, the
   display drivers simply memorized a list of clocks and dividers, and
   abused clk_set_rate() to program them individually.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 drivers/clk/Kconfig                   |   1 +
 drivers/clk/Makefile                  |   1 +
 drivers/clk/google/Kconfig            |  13 +
 drivers/clk/google/Makefile           |   3 +
 drivers/clk/google/clk-cpm-internal.h |  35 ++
 drivers/clk/google/clk-cpm-lga.c      | 271 ++++++++++++
 drivers/clk/google/clk-cpm.c          | 573 ++++++++++++++++++++++++++
 7 files changed, 897 insertions(+)
 create mode 100644 drivers/clk/google/Kconfig
 create mode 100644 drivers/clk/google/Makefile
 create mode 100644 drivers/clk/google/clk-cpm-internal.h
 create mode 100644 drivers/clk/google/clk-cpm-lga.c
 create mode 100644 drivers/clk/google/clk-cpm.c

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 37a8e35d4178..23fa8698c54c 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -517,6 +517,7 @@ source "drivers/clk/bcm/Kconfig"
 source "drivers/clk/cix/Kconfig"
 source "drivers/clk/davinci/Kconfig"
 source "drivers/clk/eswin/Kconfig"
+source "drivers/clk/google/Kconfig"
 source "drivers/clk/hisilicon/Kconfig"
 source "drivers/clk/imgtec/Kconfig"
 source "drivers/clk/imx/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 52d138d4ffc8..0465ae1295b0 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -136,6 +136,7 @@ obj-$(CONFIG_ARCH_BERLIN)		+= berlin/
 obj-y					+= cix/
 obj-$(CONFIG_COMMON_CLK_DAVINCI)	+= davinci/
 obj-$(CONFIG_COMMON_CLK_ESWIN)		+= eswin/
+obj-y					+= google/
 obj-$(CONFIG_COMMON_CLK_HISI)		+= hisilicon/
 obj-y					+= imgtec/
 obj-y					+= imx/
diff --git a/drivers/clk/google/Kconfig b/drivers/clk/google/Kconfig
new file mode 100644
index 000000000000..638299829791
--- /dev/null
+++ b/drivers/clk/google/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config GOOGLE_CPM_CLK
+	tristate "Google CPM clock driver"
+	default ARCH_GOOGLE
+	depends on ARCH_GOOGLE || COMPILE_TEST
+	depends on GOOGLE_CPM_MBA
+	help
+	  This driver allows clients to configure Google Tensor SoC LPCM clocks
+	  via CPM using the common clock framework. This covers clocks that are
+	  not supported by PFSM and PSM.
+
+	  If unsure, say N.
diff --git a/drivers/clk/google/Makefile b/drivers/clk/google/Makefile
new file mode 100644
index 000000000000..32fdd5885299
--- /dev/null
+++ b/drivers/clk/google/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_GOOGLE_CPM_CLK) += google-clk-cpm.o
+google-clk-cpm-y := clk-cpm.o clk-cpm-lga.o
diff --git a/drivers/clk/google/clk-cpm-internal.h b/drivers/clk/google/clk-cpm-internal.h
new file mode 100644
index 000000000000..f99c193a6a54
--- /dev/null
+++ b/drivers/clk/google/clk-cpm-internal.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright 2026 Google LLC
+ */
+
+#ifndef _GOOGLE_CLK_CPM_INTERNAL_H
+#define _GOOGLE_CLK_CPM_INTERNAL_H
+
+#include <linux/types.h>
+
+struct cpm_clk_data {
+	/* LPCM ID the clock belongs to */
+	u32 lpcm_id;
+	/* Index of the clock within the given LPCM */
+	u32 clock_id;
+
+	const char *name;
+	const char *parent_name;
+	bool use_smc;
+	const unsigned long *rates;
+	int nr_rates;
+	const unsigned int *divs;
+	int nr_divs;
+};
+
+struct cpm_clk_soc_data {
+	const struct cpm_clk_data *clks;
+	size_t num_clks;
+	const int *lpcm_to_pd;
+	size_t num_lpcm_to_pd;
+};
+
+extern const struct cpm_clk_soc_data cpm_clk_lga_data;
+
+#endif /* _GOOGLE_CLK_CPM_INTERNAL_H */
diff --git a/drivers/clk/google/clk-cpm-lga.c b/drivers/clk/google/clk-cpm-lga.c
new file mode 100644
index 000000000000..b1a47556cce5
--- /dev/null
+++ b/drivers/clk/google/clk-cpm-lga.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+
+#include <dt-bindings/clock/google,lga-cpm.h>
+#include <dt-bindings/power/google,lga-cpm.h>
+#include <dt-bindings/soc/google,lga-lpcm.h>
+#include <linux/kernel.h>
+
+#include "clk-cpm-internal.h"
+
+static const unsigned int clk_divs_dp[] = { 1, 2, 4 };
+static const unsigned long clk_rates_dp_osc[] = { 38400000 };
+static const unsigned long clk_rates_dp_pll[] = {
+	12587500,
+	20000000,
+	32500000,
+	37125000,
+	38400000,
+	54000000,
+	67500000,
+	74250000,
+	77000000,
+	96625000,
+	99000000,
+	117295000,
+	118800000,
+	120750000,
+	148500000,
+	159875000,
+	174250000,
+	197500000,
+	261305000,
+	266625000,
+	297000000,
+};
+
+static const unsigned long clk_rates_24576000[] = { 24576000 };
+static const unsigned long clk_rates_38400000_76800000[] = { 38400000, 76800000 };
+static const unsigned long clk_rates_100000000_150000000[] = { 100000000, 150000000 };
+static const unsigned long clk_rates_200000000[] = { 200000000 };
+static const unsigned long clk_rates_288000000[] = { 288000000 };
+static const unsigned long clk_rates_300000000[] = { 300000000 };
+static const unsigned long clk_rates_400000000[] = { 400000000 };
+
+#define CPM_CLK_LGA(domain, id, n, r) \
+	{ .lpcm_id = LGA_LPCM_##domain, .clock_id = id, .name = n, \
+	  .rates = r, .nr_rates = ARRAY_SIZE(r) }
+
+#define CPM_CLK_LGA_DIV(domain, id, n, pname, d, r) \
+	{ .lpcm_id = LGA_LPCM_##domain, .clock_id = id, .name = n, \
+	  .parent_name = pname, .divs = d, .nr_divs = ARRAY_SIZE(d), \
+	  .rates = r, .nr_rates = ARRAY_SIZE(r) }
+
+#define CPM_CLK_LGA_NO_RATES(domain, id, n) \
+	{ .lpcm_id = LGA_LPCM_##domain, .clock_id = id, .name = n }
+
+#define CPM_CLK_LGA_GATE_PARENT(domain, id, n, pname) \
+	{ .lpcm_id = LGA_LPCM_##domain, .clock_id = id, .name = n, .parent_name = pname }
+
+#define CPM_CLK_LGA_SMC(domain, id, n) \
+	{ .lpcm_id = LGA_LPCM_##domain, .clock_id = id, .name = n, .use_smc = true }
+
+static const struct cpm_clk_data cpm_clks_lga[] = {
+	/* GPCM */
+	CPM_CLK_LGA(GPCM, GPCM_HSION_PLL_DP_CLK, "gpcm-hsion-pll-dp-clk", clk_rates_dp_pll),
+	CPM_CLK_LGA_GATE_PARENT(GPCM, GPCM_DPU_PLL_DP_CLK, "gpcm-dpu-pll-dp-clk",
+				"gpcm-hsion-pll-dp-clk"),
+
+	/* CPU */
+	CPM_CLK_LGA_NO_RATES(CPU, CPU_SSWRP_TSS_CLK, "cpu-sswrp-tss-clk"),
+
+	/* LSIO_S */
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK0_CLK, "lsios-mclk0-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK1_CLK, "lsios-mclk1-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK2_CLK, "lsios-mclk2-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK3_CLK, "lsios-mclk3-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK4_CLK, "lsios-mclk4-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK5_CLK, "lsios-mclk5-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK6_CLK, "lsios-mclk6-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_MCLK7_CLK, "lsios-mclk7-clk", clk_rates_24576000),
+	CPM_CLK_LGA_NO_RATES(LSIO_S, LSIOS_GPIO_CLK, "lsios-gpio-clk"),
+	CPM_CLK_LGA(LSIO_S, LSIOS_PWM_CLK, "lsios-pwm-clk", clk_rates_24576000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_PWM_CFG_CLK, "lsios-pwm-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI0_CFG_CLK, "lsios-cli0-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI1_CFG_CLK, "lsios-cli1-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI2_CFG_CLK, "lsios-cli2-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI3_CFG_CLK, "lsios-cli3-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI4_CFG_CLK, "lsios-cli4-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI5_CFG_CLK, "lsios-cli5-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI6_CFG_CLK, "lsios-cli6-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI0_SPI_CLK, "lsios-cli0-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI1_SPI_CLK, "lsios-cli1-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI2_SPI_CLK, "lsios-cli2-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI3_SPI_CLK, "lsios-cli3-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI4_SPI_CLK, "lsios-cli4-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI5_SPI_CLK, "lsios-cli5-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI6_SPI_CLK, "lsios-cli6-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI0_I2C_CLK, "lsios-cli0-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI1_I2C_CLK, "lsios-cli1-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI2_I2C_CLK, "lsios-cli2-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI3_I2C_CLK, "lsios-cli3-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI4_I2C_CLK, "lsios-cli4-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI5_I2C_CLK, "lsios-cli5-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI6_I2C_CLK, "lsios-cli6-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI0_UART_CLK, "lsios-cli0-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI1_UART_CLK, "lsios-cli1-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI2_UART_CLK, "lsios-cli2-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI3_UART_CLK, "lsios-cli3-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI4_UART_CLK, "lsios-cli4-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI5_UART_CLK, "lsios-cli5-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI6_UART_CLK, "lsios-cli6-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI0_I3C_CLK, "lsios-cli0-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI1_I3C_CLK, "lsios-cli1-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI2_I3C_CLK, "lsios-cli2-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI3_I3C_CLK, "lsios-cli3-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI4_I3C_CLK, "lsios-cli4-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI5_I3C_CLK, "lsios-cli5-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_S, LSIOS_CLI6_I3C_CLK, "lsios-cli6-i3c-clk", clk_rates_200000000),
+
+	/* HSIO_S */
+	CPM_CLK_LGA_SMC(HSIO_S, HSIOS_GPIO_REF_TCX0_CLK, "hsios-gpio-ref-tcx0-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_S, HSIOS_SYS_CLK, "hsios-sys-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_S, HSIOS_UFS_CFG_CLK, "hsios-ufs-cfg-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_S, HSIOS_PHY_FW_CLK, "hsios-phy-fw-clk"),
+	CPM_CLK_LGA(HSIO_S, HSIOS_MPHY_REF_TCX0_CLK, "hsios-mphy-ref-clk",
+		    clk_rates_38400000_76800000),
+	CPM_CLK_LGA_SMC(HSIO_S, HSIOS_AUX_CLK, "hsios-aux-clk"),
+
+	/* HSIO_N */
+	CPM_CLK_LGA(HSIO_N, HSION_USB_CLK, "hsion-usb-clk", clk_rates_288000000),
+	CPM_CLK_LGA_DIV(HSIO_N, HSION_DP0_CLK, "hsion-dp0-clk",
+			"gpcm-hsion-pll-dp-clk", clk_divs_dp, clk_rates_dp_osc),
+	CPM_CLK_LGA_DIV(HSIO_N, HSION_DP1_CLK, "hsion-dp1-clk",
+			"gpcm-hsion-pll-dp-clk", clk_divs_dp, clk_rates_dp_osc),
+	CPM_CLK_LGA(HSIO_N, HSION_PHY_FW_CLK, "hsion-phy-fw-clk", clk_rates_400000000),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_PCIE_REF_CLK, "hsion-pcie-ref-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USBDP_SUSPEND_CLK, "hsion-usbdp-suspend-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USBC_NON_STICKY_CLK, "hsion-usbc-non-sticky-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USBC_STICKY_CLK, "hsion-usbc-sticky-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USB_DRD_BUS_CLK, "hsion-usb-drd-bus-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USB_TOP_CSR_CLK, "hsion-usb-top-csr-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_U2PHY_APB_CLK, "hsion-u2phy-apb-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USB2_PHY_RESET_CLK, "hsion-usb2-phy-reset-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_USB3DP_PHY_RESET_CLK, "hsion-usb3dp-phy-reset-clk"),
+	CPM_CLK_LGA_NO_RATES(HSIO_N, HSION_EBU_CLK, "hsion-ebu-clk"),
+
+	/* LSIO_E */
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI7_CFG_CLK, "lsioe-cli7-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI8_CFG_CLK, "lsioe-cli8-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI9_CFG_CLK, "lsioe-cli9-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI10_CFG_CLK, "lsioe-cli10-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI11_CFG_CLK, "lsioe-cli11-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI12_CFG_CLK, "lsioe-cli12-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI13_CFG_CLK, "lsioe-cli13-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI14_CFG_CLK, "lsioe-cli14-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI7_SPI_CLK, "lsioe-cli7-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI8_SPI_CLK, "lsioe-cli8-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI9_SPI_CLK, "lsioe-cli9-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI10_SPI_CLK, "lsioe-cli10-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI11_SPI_CLK, "lsioe-cli11-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI12_SPI_CLK, "lsioe-cli12-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI13_SPI_CLK, "lsioe-cli13-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI14_SPI_CLK, "lsioe-cli14-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI7_I2C_CLK, "lsioe-cli7-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI8_I2C_CLK, "lsioe-cli8-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI9_I2C_CLK, "lsioe-cli9-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI10_I2C_CLK, "lsioe-cli10-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI11_I2C_CLK, "lsioe-cli11-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI12_I2C_CLK, "lsioe-cli12-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI13_I2C_CLK, "lsioe-cli13-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI14_I2C_CLK, "lsioe-cli14-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI7_UART_CLK, "lsioe-cli7-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI8_UART_CLK, "lsioe-cli8-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI9_UART_CLK, "lsioe-cli9-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI10_UART_CLK, "lsioe-cli10-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI11_UART_CLK, "lsioe-cli11-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI12_UART_CLK, "lsioe-cli12-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI13_UART_CLK, "lsioe-cli13-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI14_UART_CLK, "lsioe-cli14-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI7_I3C_CLK, "lsioe-cli7-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI8_I3C_CLK, "lsioe-cli8-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI9_I3C_CLK, "lsioe-cli9-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI10_I3C_CLK, "lsioe-cli10-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI11_I3C_CLK, "lsioe-cli11-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI12_I3C_CLK, "lsioe-cli12-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI13_I3C_CLK, "lsioe-cli13-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_E, LSIOE_CLI14_I3C_CLK, "lsioe-cli14-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA_SMC(LSIO_E, LSIOE_GPIO_CLK, "lsioe-gpio-clk"),
+	CPM_CLK_LGA(LSIO_E, LSIOE_SPMI_CLK, "lsioe-spmi-clk", clk_rates_300000000),
+
+	/* LSIO_N */
+	CPM_CLK_LGA(LSIO_N, LSION_CLI15_CFG_CLK, "lsion-cli15-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI16_CFG_CLK, "lsion-cli16-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI15_I2C_CLK, "lsion-cli15-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI16_I2C_CLK, "lsion-cli16-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI15_UART_CLK, "lsion-cli15-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI16_UART_CLK, "lsion-cli16-uart-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI15_SPI_CLK, "lsion-cli15-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI16_SPI_CLK, "lsion-cli16-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI15_I3C_CLK, "lsion-cli15-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI16_I3C_CLK, "lsion-cli16-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA_NO_RATES(LSIO_N, LSION_GPIO_CLK, "lsion-gpio-clk"),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT0_I2C_CLK, "lsion-cli-int0-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA_NO_RATES(LSIO_N, LSION_CLI_INT0_I3C_CLK, "lsion-cli-int0-i3c-clk"),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT0_UART_CLK, "lsion-cli-int0-uart-clk",
+		    clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT0_SPI_CLK, "lsion-cli-int0-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT0_CFG_CLK, "lsion-cli-int0-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT1_I2C_CLK, "lsion-cli-int1-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA_NO_RATES(LSIO_N, LSION_CLI_INT1_I3C_CLK, "lsion-cli-int1-i3c-clk"),
+	CPM_CLK_LGA_NO_RATES(LSIO_N, LSION_CLI_INT1_UART_CLK, "lsion-cli-int1-uart-clk"),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT1_SPI_CLK, "lsion-cli-int1-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT1_CFG_CLK, "lsion-cli-int1-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT2_I2C_CLK, "lsion-cli-int2-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT2_I3C_CLK, "lsion-cli-int2-i3c-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT2_UART_CLK, "lsion-cli-int2-uart-clk",
+		    clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT2_SPI_CLK, "lsion-cli-int2-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT2_CFG_CLK, "lsion-cli-int2-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT3_I2C_CLK, "lsion-cli-int3-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA_NO_RATES(LSIO_N, LSION_CLI_INT3_I3C_CLK, "lsion-cli-int3-i3c-clk"),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT3_UART_CLK, "lsion-cli-int3-uart-clk",
+		    clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT3_SPI_CLK, "lsion-cli-int3-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT3_CFG_CLK, "lsion-cli-int3-cfg-clk", clk_rates_300000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT4_I2C_CLK, "lsion-cli-int4-i2c-clk", clk_rates_200000000),
+	CPM_CLK_LGA_NO_RATES(LSIO_N, LSION_CLI_INT4_I3C_CLK, "lsion-cli-int4-i3c-clk"),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT4_UART_CLK, "lsion-cli-int4-uart-clk",
+		    clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT4_SPI_CLK, "lsion-cli-int4-spi-clk", clk_rates_200000000),
+	CPM_CLK_LGA(LSIO_N, LSION_CLI_INT4_CFG_CLK, "lsion-cli-int4-cfg-clk", clk_rates_300000000),
+
+	/* DPU */
+	CPM_CLK_LGA_DIV(DPU, DPU_DP0_PIX_CLK, "dpu-dp0-pix-clk",
+			"gpcm-dpu-pll-dp-clk", clk_divs_dp, clk_rates_dp_osc),
+	CPM_CLK_LGA_DIV(DPU, DPU_DP1_PIX_CLK, "dpu-dp1-pix-clk",
+			"gpcm-dpu-pll-dp-clk", clk_divs_dp, clk_rates_dp_osc),
+	CPM_CLK_LGA(DPU, DPU_DSI0_PIX_CLK, "dpu-dsi0-pix-clk", clk_rates_100000000_150000000),
+	CPM_CLK_LGA(DPU, DPU_DSI1_PIX_CLK, "dpu-dsi1-pix-clk", clk_rates_100000000_150000000),
+	CPM_CLK_LGA(DPU, DPU_DSI1_PHY_CLK, "dpu-dsi1-phy-clk", clk_rates_300000000),
+};
+
+static const int lga_lpcm_to_pd[] = {
+	[0 ... LGA_LPCM_GSA] = -1,
+	[LGA_LPCM_AOC] = LGA_PD_SSWRP_AOC,
+	[LGA_LPCM_AURORA] = LGA_PD_SSWRP_AURORA,
+	[LGA_LPCM_CODEC_3P] = LGA_PD_SSWRP_CODEC_3P,
+	[LGA_LPCM_DPU] = LGA_PD_SSWRP_DPU,
+	[LGA_LPCM_EH] = LGA_PD_SSWRP_EH,
+	[LGA_LPCM_G2D] = LGA_PD_SSWRP_G2D,
+	[LGA_LPCM_GPDMA] = LGA_PD_SSWRP_GPDMA,
+	[LGA_LPCM_GPU] = LGA_PD_SSWRP_GPU,
+	[LGA_LPCM_GSW] = LGA_PD_SSWRP_GSW,
+	[LGA_LPCM_HSIO_N] = LGA_PD_SSWRP_HSIO_N,
+	[LGA_LPCM_HSIO_S] = LGA_PD_SSWRP_HSIO_S,
+	[LGA_LPCM_ISPBE] = LGA_PD_SSWRP_ISPBE,
+	[LGA_LPCM_ISPFE] = LGA_PD_SSWRP_ISPFE,
+	[LGA_LPCM_LSIO_E] = LGA_PD_SSWRP_LSIO_E,
+	[LGA_LPCM_LSIO_N] = LGA_PD_SSWRP_LSIO_N,
+	[LGA_LPCM_LSIO_S] = LGA_PD_SSWRP_LSIO_S,
+	[LGA_LPCM_TPU] = LGA_PD_SSWRP_TPU,
+};
+
+const struct cpm_clk_soc_data cpm_clk_lga_data = {
+	.clks = cpm_clks_lga,
+	.num_clks = ARRAY_SIZE(cpm_clks_lga),
+	.lpcm_to_pd = lga_lpcm_to_pd,
+	.num_lpcm_to_pd = ARRAY_SIZE(lga_lpcm_to_pd),
+};
diff --git a/drivers/clk/google/clk-cpm.c b/drivers/clk/google/clk-cpm.c
new file mode 100644
index 000000000000..5ab578dbb88e
--- /dev/null
+++ b/drivers/clk/google/clk-cpm.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2023-2026 Google LLC
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/clk-provider.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+
+#include <soc/google/goog_cpm_service_ids.h>
+#include <soc/google/google-cpm-mba.h>
+
+#include "clk-cpm-internal.h"
+
+#define MAILBOX_SEND_TIMEOUT_MS			3000
+#define LPCM_CMD_SET_LCM_CLK			0x2
+
+#define OP_ID_BITFIELD				GENMASK(31, 24)
+#define CLOCK_ID_BITFIELD			GENMASK(23, 16)
+#define LPCM_ID_BITFIELD			GENMASK(15, 8)
+#define REQ_ID_BITFIELD				GENMASK(7, 0)
+
+#define PREPARE_OP_ID				0
+#define UNPREPARE_OP_ID				1
+#define IS_ENABLE_OP_ID				2
+#define RECALC_RATE_OP_ID			3
+#define ROUND_RATE_OP_ID			4
+#define SET_RATE_OP_ID				5
+
+/* PREPARE_OP_ID / UNPREPARE_OP_ID */
+#define CLK_GATE				1
+
+#define NO_ERROR				0
+#define ERR_LPCM_NOT_SUPPORTED			(-24)
+#define ERR_LPCM_INVALID_ARGS			(-8)
+#define ERR_LPCM_TIMED_OUT			(-13)
+#define ERR_LPCM_UNAVAILABLE			(-3)
+#define ERR_LPCM_GENERIC			(-1)
+
+#define SIP_SVC_CLK_CONTROL			0x82000702
+
+#define to_goog_cpm_clk(_hw) container_of(_hw, struct goog_cpm_clk, hw)
+
+struct goog_cpm_clk {
+	struct clk_hw hw;
+	struct goog_cpm_clk_dev_data *dev_data;
+	const char *name;
+
+	unsigned int lpcm_id;
+	unsigned int clock_id;
+	const unsigned long *rates;
+	int nr_rates;
+	const unsigned int *divs;
+	int nr_divs;
+	bool use_smc;
+};
+
+struct goog_cpm_clk_dev_data {
+	struct device *dev;
+	struct cpm_client *cpm_client;
+	struct goog_cpm_clk **clks;
+	int num_clks;
+};
+
+static void goog_cpm_clk_pd_dev_release(struct device *dev)
+{
+	kfree(dev);
+}
+
+static void goog_cpm_clk_unregister_pd_dev(void *data)
+{
+	struct device *clk_dev = data;
+
+	pm_runtime_disable(clk_dev);
+	pm_genpd_remove_device(clk_dev);
+	device_unregister(clk_dev);
+}
+
+static void goog_cpm_clk_hw_unregister(void *data)
+{
+	struct clk_hw *hw = data;
+
+	clk_hw_unregister(hw);
+}
+
+static bool goog_cpm_clk_can_adjust_clk_rate(struct goog_cpm_clk *cpm_clk)
+{
+	return cpm_clk->nr_rates > 0 || cpm_clk->nr_divs > 0;
+}
+
+static int goog_cpm_clk_mba_resp_hdl(struct goog_cpm_clk *cpm_clk, int result)
+{
+	struct device *dev = cpm_clk->dev_data->dev;
+	const char *name = cpm_clk->name;
+
+	if (result >= 0)
+		return result; /* .recalc_rate returns clock's frequency instead of err code */
+
+	switch (result) {
+	case ERR_LPCM_NOT_SUPPORTED:
+		dev_err(dev, "%s: the received request_id is invalid\n", name);
+		return -EPROTO;
+	case ERR_LPCM_INVALID_ARGS:
+		dev_err(dev, "%s: invalid lpcm_id or clock_id\n", name);
+		return -EINVAL;
+	case ERR_LPCM_TIMED_OUT:
+		dev_err(dev, "%s: operation timed out\n", name);
+		return -ETIMEDOUT;
+	case ERR_LPCM_GENERIC:
+		dev_err(dev, "%s: internal error occurred\n", name);
+		return -ESERVERFAULT;
+	case ERR_LPCM_UNAVAILABLE:
+		dev_err(dev, "%s: CPM unavailable\n", name);
+		return -ENODATA;
+	default:
+		dev_err(dev, "%s: unknown error response from CPM (%d)\n", name, result);
+		return -EPROTO;
+	}
+}
+
+static int goog_cpm_clk_send_mba_mail(struct goog_cpm_clk *cpm_clk, u8 op_id,
+				      unsigned long *clk_param)
+{
+	struct device *dev = cpm_clk->dev_data->dev;
+	struct cpm_payload resp_msg;
+	struct cpm_payload req_msg = {};
+	struct cpm_request cpm_req = {
+		.msg_type = REQUEST_MSG,
+		.req_msg = &req_msg,
+		.resp_msg = &resp_msg,
+		.dst_id = CPM_COMMON_LPCM_SERVICE,
+		.tout_ms = MAILBOX_SEND_TIMEOUT_MS,
+	};
+	int ret;
+
+	req_msg.payload[0] = FIELD_PREP(REQ_ID_BITFIELD, LPCM_CMD_SET_LCM_CLK) |
+			     FIELD_PREP(LPCM_ID_BITFIELD, cpm_clk->lpcm_id) |
+			     FIELD_PREP(CLOCK_ID_BITFIELD, cpm_clk->clock_id) |
+			     FIELD_PREP(OP_ID_BITFIELD, op_id);
+
+	if (op_id == SET_RATE_OP_ID || op_id == PREPARE_OP_ID ||
+	    op_id == UNPREPARE_OP_ID) {
+		if (!clk_param) {
+			dev_err(dev, "clk_param is NULL for op_id %d\n", op_id);
+			return -EINVAL;
+		}
+		req_msg.payload[1] = *clk_param;
+	}
+
+	dev_dbg(dev, "lpcm_id %d, clock_id %d: send mba mail: [%d, %d, %d]\n",
+		cpm_clk->lpcm_id, cpm_clk->clock_id, req_msg.payload[0],
+		req_msg.payload[1], req_msg.payload[2]);
+
+	ret = cpm_send_message(cpm_clk->dev_data->cpm_client, &cpm_req);
+	if (ret < 0) {
+		dev_err(dev, "lpcm_id %d, clock_id %d: failed to send request to CPM, ret=%d\n",
+			cpm_clk->lpcm_id, cpm_clk->clock_id, ret);
+		return ret;
+	}
+
+	dev_dbg(dev, "got resp from %u, data %d.\n", cpm_req.dst_id,
+		resp_msg.payload[0]);
+	return goog_cpm_clk_mba_resp_hdl(cpm_clk, resp_msg.payload[0]);
+}
+
+static int goog_cpm_clk_send_mbox_msg_set_gate(struct goog_cpm_clk *cpm_clk,
+					       bool enable)
+{
+	int op_id = enable ? PREPARE_OP_ID : UNPREPARE_OP_ID;
+	unsigned long param = CLK_GATE;
+
+	return goog_cpm_clk_send_mba_mail(cpm_clk, op_id, &param);
+}
+
+static int goog_cpm_clk_send_mbox_msg_set_rate(struct goog_cpm_clk *cpm_clk,
+					       unsigned long rate)
+{
+	return goog_cpm_clk_send_mba_mail(cpm_clk, SET_RATE_OP_ID, &rate);
+}
+
+static int goog_cpm_clk_send_mbox_msg_recalc_rate(struct goog_cpm_clk *cpm_clk)
+{
+	return goog_cpm_clk_send_mba_mail(cpm_clk, RECALC_RATE_OP_ID, NULL);
+}
+
+static int goog_cpm_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+	unsigned long param = rate;
+
+	for (int i = 0; i < cpm_clk->nr_divs; i++) {
+		if (parent_rate / cpm_clk->divs[i] == rate) {
+			param = cpm_clk->divs[i];
+			break;
+		}
+	}
+
+	for (int i = 0; i < cpm_clk->nr_rates; i++) {
+		if (cpm_clk->rates[i] == rate) {
+			param = rate;
+			break;
+		}
+	}
+
+	dev_dbg(cpm_clk->dev_data->dev,
+		"%s: lpcm_id %d, clock_id %d: set rate %lu (param %lu)\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id, rate, param);
+	return goog_cpm_clk_send_mbox_msg_set_rate(cpm_clk, param);
+}
+
+static unsigned long goog_cpm_clk_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+	int ret;
+
+	dev_dbg(cpm_clk->dev_data->dev, "%s: lpcm_id %d, clock_id %d: get rate\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id);
+	ret = goog_cpm_clk_send_mbox_msg_recalc_rate(cpm_clk);
+	if (ret < 0)
+		return 0;
+
+	for (int i = 0; i < cpm_clk->nr_divs; i++) {
+		if (ret == cpm_clk->divs[i])
+			return parent_rate / ret;
+	}
+
+	return ret;
+}
+
+static int goog_cpm_clk_determine_rate(struct clk_hw *hw,
+				       struct clk_rate_request *req)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+	unsigned long best_rate = ULONG_MAX;
+	unsigned long max_rate = 0;
+
+	dev_dbg(cpm_clk->dev_data->dev, "%s: lpcm_id %d, clock_id %d: determine rate for %lu\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id, req->rate);
+
+	for (int i = 0; i < cpm_clk->nr_rates; i++) {
+		unsigned long rate = cpm_clk->rates[i];
+
+		if (rate >= req->rate && rate < best_rate)
+			best_rate = rate;
+		if (rate > max_rate)
+			max_rate = rate;
+	}
+
+	for (int i = 0; i < cpm_clk->nr_divs; i++) {
+		unsigned long rate = req->best_parent_rate / cpm_clk->divs[i];
+
+		if (rate >= req->rate && rate < best_rate)
+			best_rate = rate;
+		if (rate > max_rate)
+			max_rate = rate;
+	}
+
+	if (best_rate != ULONG_MAX) {
+		req->rate = best_rate;
+		return 0;
+	}
+	if (max_rate > 0) {
+		req->rate = max_rate;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static inline int lpcm_smc(struct clk_hw *hw, bool enable)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(SIP_SVC_CLK_CONTROL, cpm_clk->lpcm_id, cpm_clk->clock_id,
+		      enable, 0, 0, 0, 0, &res);
+
+	WARN_ON(res.a0);
+	if ((unsigned long)res.a1) {
+		dev_err(cpm_clk->dev_data->dev,
+			"Failed LPCM SMC: %d clk_id: %d, enable: %d res.a1: %lu\n",
+			cpm_clk->lpcm_id, cpm_clk->clock_id, enable, res.a1);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int goog_cpm_clk_prepare(struct clk_hw *hw)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+
+	dev_dbg(cpm_clk->dev_data->dev, "%s: lpcm_id %d, clock_id %d: enable\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id);
+
+	return goog_cpm_clk_send_mbox_msg_set_gate(cpm_clk, true);
+}
+
+static void goog_cpm_clk_unprepare(struct clk_hw *hw)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+
+	dev_dbg(cpm_clk->dev_data->dev, "%s: lpcm_id %d, clock_id %d: disable\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id);
+
+	goog_cpm_clk_send_mbox_msg_set_gate(cpm_clk, false);
+}
+
+static int goog_cpm_clk_smc_enable(struct clk_hw *hw)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+
+	dev_dbg(cpm_clk->dev_data->dev, "%s: lpcm_id %d, clock_id %d: enable\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id);
+
+	return lpcm_smc(hw, true);
+}
+
+static void goog_cpm_clk_smc_disable(struct clk_hw *hw)
+{
+	struct goog_cpm_clk *cpm_clk = to_goog_cpm_clk(hw);
+
+	dev_dbg(cpm_clk->dev_data->dev, "%s: lpcm_id %d, clock_id %d: disable\n",
+		cpm_clk->name, cpm_clk->lpcm_id, cpm_clk->clock_id);
+
+	lpcm_smc(hw, false);
+}
+
+static const struct clk_ops goog_cpm_mutable_rate_clk_ops = {
+	.set_rate = goog_cpm_clk_set_rate,
+	.recalc_rate = goog_cpm_clk_recalc_rate,
+	.determine_rate = goog_cpm_clk_determine_rate,
+	.prepare = goog_cpm_clk_prepare,
+	.unprepare = goog_cpm_clk_unprepare,
+};
+
+static const struct clk_ops goog_cpm_gate_clk_ops = {
+	.prepare = goog_cpm_clk_prepare,
+	.unprepare = goog_cpm_clk_unprepare,
+};
+
+static const struct clk_ops goog_cpm_gate_smc_clk_ops = {
+	.enable = goog_cpm_clk_smc_enable,
+	.disable = goog_cpm_clk_smc_disable,
+};
+
+static struct device *goog_cpm_clk_attach_pd(struct device *dev, const char *name,
+					     int pd_id)
+{
+	struct of_phandle_args genpdspec = {
+		.np = dev->of_node,
+		.args_count = 1,
+		.args[0] = pd_id,
+	};
+	struct device *clk_dev;
+	int err;
+
+	/*
+	 * No need for a new device if there's no domain to attach. Just reuse
+	 * the controller device.
+	 */
+	if (pd_id < 0)
+		return dev;
+
+	clk_dev = kzalloc_obj(*clk_dev, GFP_KERNEL);
+	if (!clk_dev)
+		return ERR_PTR(-ENOMEM);
+
+	device_initialize(clk_dev);
+	clk_dev->parent = dev;
+	clk_dev->release = goog_cpm_clk_pd_dev_release;
+	dev_set_name(clk_dev, "%s:%s", dev_name(dev), name);
+
+	err = device_add(clk_dev);
+	if (err) {
+		put_device(clk_dev);
+		return ERR_PTR(err);
+	}
+
+	err = of_genpd_add_device(&genpdspec, clk_dev);
+	if (err) {
+		device_unregister(clk_dev);
+		if (err == -ENOENT)
+			err = -EPROBE_DEFER;
+		return ERR_PTR(dev_err_probe(dev, err,
+					     "failed to attach %s to power domain %d\n",
+					     name, pd_id));
+	}
+
+	pm_runtime_enable(clk_dev);
+
+	err = devm_add_action_or_reset(dev, goog_cpm_clk_unregister_pd_dev, clk_dev);
+	if (err)
+		return ERR_PTR(err);
+
+	return clk_dev;
+}
+
+static struct goog_cpm_clk *
+goog_cpm_clk_register_clk(struct device *dev,
+			  const struct cpm_clk_data *clk_data,
+			  const struct cpm_clk_soc_data *soc_data,
+			  struct goog_cpm_clk_dev_data *dev_data)
+{
+	struct goog_cpm_clk *cpm_clk;
+	struct clk_init_data init = {};
+	const char *name = clk_data->name;
+	struct device *clk_dev;
+	int pd_id = -1;
+	int err;
+
+	cpm_clk = devm_kzalloc(dev, sizeof(*cpm_clk), GFP_KERNEL);
+	if (!cpm_clk)
+		return ERR_PTR(-ENOMEM);
+
+	cpm_clk->name = name;
+	cpm_clk->lpcm_id = clk_data->lpcm_id;
+	cpm_clk->clock_id = clk_data->clock_id;
+	cpm_clk->use_smc = clk_data->use_smc;
+	cpm_clk->rates = clk_data->rates;
+	cpm_clk->nr_rates = clk_data->nr_rates;
+	cpm_clk->divs = clk_data->divs;
+	cpm_clk->nr_divs = clk_data->nr_divs;
+	cpm_clk->dev_data = dev_data;
+
+	if (soc_data->lpcm_to_pd && clk_data->lpcm_id < soc_data->num_lpcm_to_pd)
+		pd_id = soc_data->lpcm_to_pd[clk_data->lpcm_id];
+
+	clk_dev = goog_cpm_clk_attach_pd(dev, name, pd_id);
+	if (IS_ERR(clk_dev))
+		return ERR_CAST(clk_dev);
+
+	init.name = name;
+	init.flags = CLK_GET_RATE_NOCACHE;
+	if (clk_data->parent_name) {
+		init.parent_names = &clk_data->parent_name;
+		init.num_parents = 1;
+	}
+	if (goog_cpm_clk_can_adjust_clk_rate(cpm_clk))
+		init.ops = &goog_cpm_mutable_rate_clk_ops;
+	else if (cpm_clk->use_smc)
+		init.ops = &goog_cpm_gate_smc_clk_ops;
+	else
+		init.ops = &goog_cpm_gate_clk_ops;
+	cpm_clk->hw.init = &init;
+
+	err = clk_hw_register(clk_dev, &cpm_clk->hw);
+	if (err) {
+		dev_err(dev, "%s: failed to register clk hw: %d\n", name, err);
+		return ERR_PTR(err);
+	}
+
+	/*
+	 * Attach teardown to the controller dev (not clk_dev) so the clock is
+	 * unregistered with driver teardown, not with (late) device cleanup.
+	 */
+	err = devm_add_action_or_reset(dev, goog_cpm_clk_hw_unregister, &cpm_clk->hw);
+	if (err)
+		return ERR_PTR(err);
+
+	return cpm_clk;
+}
+
+static struct clk_hw *
+goog_cpm_clk_of_xlate(struct of_phandle_args *clkspec, void *data)
+{
+	struct goog_cpm_clk_dev_data *dev_data = data;
+	u32 lpcm_id, clock_id;
+	int i;
+
+	if (clkspec->args_count != 2)
+		return ERR_PTR(-EINVAL);
+
+	lpcm_id = clkspec->args[0];
+	clock_id = clkspec->args[1];
+
+	for (i = 0; i < dev_data->num_clks; i++) {
+		if (dev_data->clks[i]->lpcm_id == lpcm_id &&
+		    dev_data->clks[i]->clock_id == clock_id)
+			return &dev_data->clks[i]->hw;
+	}
+
+	return ERR_PTR(-ENOENT);
+}
+
+static const struct of_device_id cpm_clk_of_match[] = {
+	{ .compatible = "google,lga-cpm", .data = &cpm_clk_lga_data },
+	{ }
+};
+
+static int goog_cpm_clk_controller_probe(struct auxiliary_device *auxdev,
+					 const struct auxiliary_device_id *id)
+{
+	struct device *dev = &auxdev->dev;
+	struct device_node *node = dev->of_node;
+	struct goog_cpm_clk_dev_data *dev_data;
+	const struct of_device_id *match;
+	const struct cpm_clk_soc_data *soc_data;
+	int ret, i;
+
+	if (!node) {
+		dev_err(dev, "No device tree node associated with device\n");
+		return -ENODEV;
+	}
+
+	match = of_match_node(cpm_clk_of_match, node);
+	if (!match) {
+		dev_err(dev, "unsupported CPM compatible\n");
+		return -ENODEV;
+	}
+	soc_data = match->data;
+
+	dev_data = devm_kzalloc(dev, sizeof(*dev_data), GFP_KERNEL);
+	if (!dev_data)
+		return -ENOMEM;
+
+	dev_data->dev = dev;
+
+	dev_data->cpm_client = devm_cpm_request_client(dev, 0, NULL, NULL);
+	if (IS_ERR(dev_data->cpm_client))
+		return dev_err_probe(dev, PTR_ERR(dev_data->cpm_client),
+				     "Failed to request cpm client\n");
+
+	dev_data->num_clks = soc_data->num_clks;
+	dev_data->clks = devm_kcalloc(dev, dev_data->num_clks,
+				      sizeof(*dev_data->clks), GFP_KERNEL);
+	if (!dev_data->clks)
+		return -ENOMEM;
+
+	auxiliary_set_drvdata(auxdev, dev_data);
+
+	for (i = 0; i < dev_data->num_clks; i++) {
+		struct goog_cpm_clk *cpm_clk;
+
+		cpm_clk = goog_cpm_clk_register_clk(dev, &soc_data->clks[i], soc_data, dev_data);
+		if (IS_ERR(cpm_clk))
+			return PTR_ERR(cpm_clk);
+
+		dev_data->clks[i] = cpm_clk;
+	}
+
+	ret = devm_of_clk_add_hw_provider(dev, goog_cpm_clk_of_xlate, dev_data);
+	if (ret) {
+		dev_err(dev, "failed to add clock provider: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct auxiliary_device_id goog_cpm_clk_id_table[] = {
+	{ .name = "google_cpm_mba.clk" },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, goog_cpm_clk_id_table);
+
+static struct auxiliary_driver goog_cpm_clk_driver = {
+	.probe = goog_cpm_clk_controller_probe,
+	.name = "google-clk-cpm",
+	.id_table = goog_cpm_clk_id_table,
+};
+
+module_auxiliary_driver(goog_cpm_clk_driver);
+
+MODULE_DESCRIPTION("Google CPM Clock Driver");
+MODULE_LICENSE("GPL");
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 6/9] reset: google: Add CPM reset driver
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (4 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 5/9] clk: google: Add CPM clock driver Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 14:03   ` Philipp Zabel
  2026-10-26 21:43 ` [PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver Brian Norris
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

Google Tensor SoCs (used on Pixel phones) manage many resources
(including resets) using a Central Power Manager (CPM) co-processor. We
communicate with CPM using mailboxes, abstracted in the
cpm_request_client(), etc., API.

For posterity, I list a summary of changes made relative to the
downstream Pixel driver:

* Switch to auxiliary bus, as a child of a CPM device

* Use devm more heavily

* Avoid representing each reset as its own Device Tree node. Use a
  limited per-SoC table to describe any additional per-SoC details
  instead.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 drivers/reset/Kconfig            |   1 +
 drivers/reset/Makefile           |   1 +
 drivers/reset/google/Kconfig     |  11 +
 drivers/reset/google/Makefile    |   2 +
 drivers/reset/google/reset-cpm.c | 367 +++++++++++++++++++++++++++++++
 5 files changed, 382 insertions(+)
 create mode 100644 drivers/reset/google/Kconfig
 create mode 100644 drivers/reset/google/Makefile
 create mode 100644 drivers/reset/google/reset-cpm.c

diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index eda9c698a95f..37dbddcd620f 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -451,6 +451,7 @@ config RESET_ZYNQMP
 	  This enables the reset controller driver for Xilinx ZynqMP SoCs.
 
 source "drivers/reset/amlogic/Kconfig"
+source "drivers/reset/google/Kconfig"
 source "drivers/reset/hisilicon/Kconfig"
 source "drivers/reset/spacemit/Kconfig"
 source "drivers/reset/starfive/Kconfig"
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 489fafd6bbe3..9754c274766c 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-y += core.o
 obj-y += amlogic/
+obj-y += google/
 obj-y += hisilicon/
 obj-y += spacemit/
 obj-y += starfive/
diff --git a/drivers/reset/google/Kconfig b/drivers/reset/google/Kconfig
new file mode 100644
index 000000000000..38431b4f9001
--- /dev/null
+++ b/drivers/reset/google/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config RESET_CPM
+	tristate "Google CPM Reset Controller"
+	depends on ARCH_GOOGLE || COMPILE_TEST
+	depends on GOOGLE_CPM_MBA
+	help
+	  Driver support for reset controllers found on Google Tensor SoCs,
+	  operating over the CPM mailbox interface. This allows peripheral
+	  drivers to assert and deassert resets on LPCM hardware blocks.
+
+	  If unsure, say N.
diff --git a/drivers/reset/google/Makefile b/drivers/reset/google/Makefile
new file mode 100644
index 000000000000..437c19459464
--- /dev/null
+++ b/drivers/reset/google/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_RESET_CPM) += reset-cpm.o
diff --git a/drivers/reset/google/reset-cpm.c b/drivers/reset/google/reset-cpm.c
new file mode 100644
index 000000000000..5f144f1ad5f2
--- /dev/null
+++ b/drivers/reset/google/reset-cpm.c
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Device driver to control Google SoC LPCM resets via CPM.
+ * Copyright 2023-2026 Google LLC
+ */
+
+#include <dt-bindings/soc/google,lga-lpcm.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/reset-controller.h>
+#include <soc/google/goog_cpm_service_ids.h>
+#include <soc/google/google-cpm-mba.h>
+
+#define MAILBOX_SEND_TIMEOUT_MS		3000
+#define LPCM_CMD_SET_RST		0x4
+
+#define OP_ID_BITFIELD			GENMASK(31, 24)
+#define RST_ID_BITFIELD			GENMASK(23, 16)
+#define LPCM_ID_BITFIELD		GENMASK(15, 8)
+#define REQ_ID_BITFIELD			GENMASK(7, 0)
+
+#define ASSERT_OP_ID			0
+#define DEASSERT_OP_ID			1
+
+#define NO_ERROR			0
+#define ERR_LPCM_NOT_SUPPORTED		(-24)
+#define ERR_LPCM_INVALID_ARGS		(-8)
+#define ERR_LPCM_TIMED_OUT		(-13)
+#define ERR_LPCM_GENERIC		(-1)
+
+#define CPM_RST_LPCM_ID_MASK		GENMASK(31, 16)
+#define CPM_RST_ID_MASK			GENMASK(15, 0)
+
+static inline unsigned long to_rstc_idx(u32 lpcm_id, u32 rst_id)
+{
+	return FIELD_PREP(CPM_RST_LPCM_ID_MASK, lpcm_id) |
+	       FIELD_PREP(CPM_RST_ID_MASK, rst_id);
+}
+
+static inline u32 lpcm_from_idx(unsigned long idx)
+{
+	return FIELD_GET(CPM_RST_LPCM_ID_MASK, idx);
+}
+
+static inline u32 rst_from_idx(unsigned long idx)
+{
+	return FIELD_GET(CPM_RST_ID_MASK, idx);
+}
+
+struct cpm_reset_data {
+	u32 lpcm_id;
+	u32 nr_resets;
+	const char *name;
+};
+
+struct cpm_reset_soc_data {
+	const struct cpm_reset_data *resets;
+	size_t num_resets;
+};
+
+struct goog_cpm_rst_provider {
+	struct device *dev;
+	struct reset_controller_dev rcdev;
+	struct cpm_client *cpm_client;
+	struct dentry *debugfs_root;
+	struct goog_cpm_rst_debugfs_entry *dbg_entries;
+	const struct cpm_reset_soc_data *soc_data;
+};
+
+#define to_provider(rc) container_of(rc, struct goog_cpm_rst_provider, rcdev)
+
+struct goog_cpm_rst_debugfs_entry {
+	struct goog_cpm_rst_provider *provider;
+	u32 lpcm_id;
+};
+
+static int goog_cpm_rst_mba_check_ret(struct device *dev, int ret)
+{
+	switch (ret) {
+	case NO_ERROR:
+		return 0;
+	case ERR_LPCM_NOT_SUPPORTED:
+		dev_err(dev, "the received request_id is invalid\n");
+		return -EPROTO;
+	case ERR_LPCM_INVALID_ARGS:
+		dev_err(dev, "invalid lpcm_id or rst_id\n");
+		return -EINVAL;
+	case ERR_LPCM_TIMED_OUT:
+		dev_err(dev, "operation timed out\n");
+		return -ETIMEDOUT;
+	case ERR_LPCM_GENERIC:
+		dev_err(dev, "internal error occurred\n");
+		return -EREMOTEIO;
+	default:
+		dev_err(dev, "unknown error response from CPM\n");
+		return -EPROTO;
+	}
+}
+
+static int goog_cpm_rst_mba_resp_hdl(struct device *dev, const struct cpm_request *cpm_req)
+{
+	int dst = cpm_req->dst_id;
+	int ret = cpm_req->resp_msg->payload[0];
+
+	dev_dbg(dev, "got resp from %u, data %d.\n", dst, ret);
+
+	return goog_cpm_rst_mba_check_ret(dev, ret);
+}
+
+static int goog_cpm_rst_send_mba_mail(struct goog_cpm_rst_provider *provider,
+				      u32 lpcm_id, unsigned long rst_id, int op_id)
+{
+	struct device *dev = provider->dev;
+	struct cpm_payload resp_msg;
+	struct cpm_payload req_msg = {};
+	struct cpm_request cpm_req = {
+		.msg_type = REQUEST_MSG,
+		.req_msg = &req_msg,
+		.resp_msg = &resp_msg,
+		.dst_id = CPM_COMMON_LPCM_SERVICE,
+		.tout_ms = MAILBOX_SEND_TIMEOUT_MS,
+	};
+	int ret;
+
+	req_msg.payload[0] = FIELD_PREP(REQ_ID_BITFIELD, LPCM_CMD_SET_RST) |
+			     FIELD_PREP(LPCM_ID_BITFIELD, lpcm_id) |
+			     FIELD_PREP(RST_ID_BITFIELD, rst_id) |
+			     FIELD_PREP(OP_ID_BITFIELD, op_id);
+
+	dev_dbg(dev, "lpcm_id %d, rst_id %lu: send mba mail: [%u, %u, %u]\n",
+		lpcm_id, rst_id, req_msg.payload[0], req_msg.payload[1],
+		req_msg.payload[2]);
+
+	ret = cpm_send_message(provider->cpm_client, &cpm_req);
+	if (ret < 0) {
+		dev_err(dev,
+			"lpcm_id %d, rst_id %lu: failed to send request to CPM, ret=%d\n",
+			lpcm_id, rst_id, ret);
+		return ret;
+	}
+
+	return goog_cpm_rst_mba_resp_hdl(dev, &cpm_req);
+}
+
+static int goog_cpm_rst_assert(struct reset_controller_dev *rcdev,
+			       unsigned long idx)
+{
+	struct goog_cpm_rst_provider *provider = to_provider(rcdev);
+	u32 lpcm_id = lpcm_from_idx(idx);
+	u32 rst_id = rst_from_idx(idx);
+
+	dev_dbg(rcdev->dev, "assert reset id=%u of lpcm %u\n", rst_id, lpcm_id);
+	return goog_cpm_rst_send_mba_mail(provider, lpcm_id, rst_id, ASSERT_OP_ID);
+}
+
+static int goog_cpm_rst_deassert(struct reset_controller_dev *rcdev,
+				 unsigned long idx)
+{
+	struct goog_cpm_rst_provider *provider = to_provider(rcdev);
+	u32 lpcm_id = lpcm_from_idx(idx);
+	u32 rst_id = rst_from_idx(idx);
+
+	dev_dbg(rcdev->dev, "deassert reset id=%u of lpcm %u\n", rst_id, lpcm_id);
+	return goog_cpm_rst_send_mba_mail(provider, lpcm_id, rst_id, DEASSERT_OP_ID);
+}
+
+static const struct reset_control_ops goog_cpm_rst_ops = {
+	.assert = goog_cpm_rst_assert,
+	.deassert = goog_cpm_rst_deassert,
+};
+
+static int goog_cpm_rst_debugfs_assert_set(void *data, u64 rst_id)
+{
+	struct goog_cpm_rst_debugfs_entry *entry = data;
+	unsigned long idx = to_rstc_idx(entry->lpcm_id, rst_id);
+
+	return goog_cpm_rst_assert(&entry->provider->rcdev, idx);
+}
+
+static int goog_cpm_rst_debugfs_deassert_set(void *data, u64 rst_id)
+{
+	struct goog_cpm_rst_debugfs_entry *entry = data;
+	unsigned long idx = to_rstc_idx(entry->lpcm_id, rst_id);
+
+	return goog_cpm_rst_deassert(&entry->provider->rcdev, idx);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(goog_cpm_rst_assert_debugfs_fops, NULL,
+			 goog_cpm_rst_debugfs_assert_set, "%llu\n");
+
+DEFINE_DEBUGFS_ATTRIBUTE(goog_cpm_rst_deassert_debugfs_fops, NULL,
+			 goog_cpm_rst_debugfs_deassert_set, "%llu\n");
+
+static void goog_cpm_rst_add_debugfs(struct goog_cpm_rst_debugfs_entry *entry,
+				     struct dentry *debugfs_root,
+				     const char *name)
+{
+	struct dentry *d = debugfs_create_dir(name, debugfs_root);
+
+	debugfs_create_file("assert", 0220, d, entry,
+			    &goog_cpm_rst_assert_debugfs_fops);
+	debugfs_create_file("deassert", 0220, d, entry,
+			    &goog_cpm_rst_deassert_debugfs_fops);
+}
+
+static int goog_cpm_rst_get_nr_resets(const struct cpm_reset_soc_data *soc_data, u32 lpcm_id)
+{
+	int i;
+
+	for (i = 0; i < soc_data->num_resets; i++)
+		if (soc_data->resets[i].lpcm_id == lpcm_id)
+			return soc_data->resets[i].nr_resets;
+
+	return -EINVAL;
+}
+
+static int goog_cpm_rst_of_xlate(struct reset_controller_dev *rcdev,
+				 const struct of_phandle_args *reset_spec)
+{
+	struct goog_cpm_rst_provider *provider = to_provider(rcdev);
+	u32 lpcm_id, rst_id;
+	int max_resets;
+
+	if (reset_spec->args_count != 2)
+		return -EINVAL;
+
+	lpcm_id = reset_spec->args[0];
+	rst_id = reset_spec->args[1];
+
+	max_resets = goog_cpm_rst_get_nr_resets(provider->soc_data, lpcm_id);
+	if (max_resets < 0)
+		return max_resets;
+
+	if (rst_id >= max_resets)
+		return -EINVAL;
+
+	return to_rstc_idx(lpcm_id, rst_id);
+}
+
+static const struct cpm_reset_data cpm_resets_lga_entries[] = {
+	{
+		.lpcm_id = LGA_LPCM_HSIO_N,
+		.nr_resets = 16,
+		.name = "hsio-n",
+	},
+	{
+		.lpcm_id = LGA_LPCM_HSIO_S,
+		.nr_resets = 5,
+		.name = "hsio-s",
+	},
+	{
+		.lpcm_id = LGA_LPCM_LSIO_S,
+		.nr_resets = 45,
+		.name = "lsio-s",
+	},
+	{
+		.lpcm_id = LGA_LPCM_LSIO_N,
+		.nr_resets = 43,
+		.name = "lsio-n",
+	},
+	{
+		.lpcm_id = LGA_LPCM_LSIO_E,
+		.nr_resets = 50,
+		.name = "lsio-e",
+	},
+};
+
+static const struct cpm_reset_soc_data cpm_resets_lga = {
+	.resets = cpm_resets_lga_entries,
+	.num_resets = ARRAY_SIZE(cpm_resets_lga_entries),
+};
+
+static const struct of_device_id cpm_rst_of_match[] = {
+	{ .compatible = "google,lga-cpm", .data = &cpm_resets_lga },
+	{ /* sentinel */ }
+};
+
+static int goog_cpm_rst_probe(struct auxiliary_device *auxdev,
+			      const struct auxiliary_device_id *id)
+{
+	struct device *dev = &auxdev->dev;
+	struct goog_cpm_rst_provider *provider;
+	const struct of_device_id *match;
+	int i, ret;
+
+	match = of_match_node(cpm_rst_of_match, dev->of_node);
+	if (!match) {
+		dev_err(dev, "no matching SoC data found\n");
+		return -ENODEV;
+	}
+
+	provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL);
+	if (!provider)
+		return -ENOMEM;
+
+	provider->dev = dev;
+	provider->soc_data = match->data;
+
+	provider->cpm_client = devm_cpm_request_client(dev, 0, NULL, NULL);
+	if (IS_ERR(provider->cpm_client))
+		return dev_err_probe(dev, PTR_ERR(provider->cpm_client),
+				     "Failed to request cpm client\n");
+	auxiliary_set_drvdata(auxdev, provider);
+
+	provider->rcdev.dev = dev;
+	provider->rcdev.owner = THIS_MODULE;
+	provider->rcdev.of_node = dev->of_node;
+	provider->rcdev.ops = &goog_cpm_rst_ops;
+	provider->rcdev.of_reset_n_cells = 2;
+	provider->rcdev.of_xlate = goog_cpm_rst_of_xlate;
+
+	ret = devm_reset_controller_register(dev, &provider->rcdev);
+	if (ret < 0) {
+		dev_err(dev, "failed to register reset controller: %d\n", ret);
+		return ret;
+	}
+
+	provider->debugfs_root = debugfs_create_dir(dev_name(dev), NULL);
+	if (!IS_ERR(provider->debugfs_root)) {
+		provider->dbg_entries = devm_kcalloc(dev, provider->soc_data->num_resets,
+						     sizeof(*provider->dbg_entries), GFP_KERNEL);
+		if (!provider->dbg_entries) {
+			debugfs_remove_recursive(provider->debugfs_root);
+			return -ENOMEM;
+		}
+
+		for (i = 0; i < provider->soc_data->num_resets; i++) {
+			provider->dbg_entries[i].provider = provider;
+			provider->dbg_entries[i].lpcm_id = provider->soc_data->resets[i].lpcm_id;
+			goog_cpm_rst_add_debugfs(&provider->dbg_entries[i],
+						 provider->debugfs_root,
+						 provider->soc_data->resets[i].name);
+		}
+	}
+
+	return 0;
+}
+
+static void goog_cpm_rst_remove(struct auxiliary_device *auxdev)
+{
+	struct goog_cpm_rst_provider *provider = auxiliary_get_drvdata(auxdev);
+
+	debugfs_remove_recursive(provider->debugfs_root);
+}
+
+static const struct auxiliary_device_id goog_cpm_rst_id_table[] = {
+	{ .name = "google_cpm_mba.reset" },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, goog_cpm_rst_id_table);
+
+static struct auxiliary_driver goog_cpm_rst_driver = {
+	.probe = goog_cpm_rst_probe,
+	.remove = goog_cpm_rst_remove,
+	.name = "google-reset-cpm",
+	.id_table = goog_cpm_rst_id_table,
+};
+
+module_auxiliary_driver(goog_cpm_rst_driver);
+
+MODULE_DESCRIPTION("Google CPM Reset Driver");
+MODULE_LICENSE("GPL");
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (5 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 6/9] reset: google: Add CPM reset driver Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 20:23   ` Thomas Gleixner
  2026-10-26 21:43 ` [PATCH 8/9] arm64: dts: google: Add Laguna CPM node Brian Norris
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Central Power Manager (CPM) on Google Tensor SoCs can signal
events/interrupts over its mailbox communication protocol. Today, the
downstream driver for these chips only supports interrupt notification,
although the CPM firmware may also have mask/unmask support (not
currently used) that could be added later if needed.

In practice, this driver is used for RTC alarms and for events driven by
the Always-on Compute (AoC) subsystem.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 drivers/irqchip/Kconfig          |  10 +++
 drivers/irqchip/Makefile         |   1 +
 drivers/irqchip/irq-google-cpm.c | 123 +++++++++++++++++++++++++++++++
 3 files changed, 134 insertions(+)
 create mode 100644 drivers/irqchip/irq-google-cpm.c

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index ca915fca0ef5..47f9739b1d74 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -556,6 +556,16 @@ config CSKY_APB_INTC
 	  by C-SKY single core SOC system. It uses mmio map apb-bus to visit
 	  the controller's register.
 
+config GOOGLE_CPM_IRQ
+	tristate "Google Tensor CPM interrupt controller"
+	depends on GOOGLE_CPM_MBA
+	depends on ARCH_GOOGLE || COMPILE_TEST
+	select IRQ_DOMAIN
+	help
+	  Support for the Central Power Manager (CPM) interrupt controller
+	  found on Google Tensor SoCs. This controller routes interrupts from
+	  the CPM to the Application Processor.
+
 config IMX_IRQSTEER
 	bool "i.MX IRQSTEER support"
 	depends on ARCH_MXC || ARCH_S32 || COMPILE_TEST
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 16a5fd126c9a..ca10888687fd 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_QCOM_PDC)			+= qcom-pdc.o
 obj-$(CONFIG_QCOM_MPM)			+= irq-qcom-mpm.o
 obj-$(CONFIG_CSKY_MPINTC)		+= irq-csky-mpintc.o
 obj-$(CONFIG_CSKY_APB_INTC)		+= irq-csky-apb-intc.o
+obj-$(CONFIG_GOOGLE_CPM_IRQ)		+= irq-google-cpm.o
 obj-$(CONFIG_RISCV_INTC)		+= irq-riscv-intc.o
 obj-$(CONFIG_RISCV_APLIC)		+= irq-riscv-aplic-main.o irq-riscv-aplic-direct.o
 obj-$(CONFIG_RISCV_APLIC_MSI)		+= irq-riscv-aplic-msi.o
diff --git a/drivers/irqchip/irq-google-cpm.c b/drivers/irqchip/irq-google-cpm.c
new file mode 100644
index 000000000000..a235cc1cfe7c
--- /dev/null
+++ b/drivers/irqchip/irq-google-cpm.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Google CPM IRQ chip driver
+ *
+ * Copyright 2023-2026 Google LLC
+ */
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <soc/google/goog_cpm_service_ids.h>
+#include <soc/google/google-cpm-mba.h>
+
+#define CPM_IRQ_COUNT				512
+
+struct cpm_irq_info {
+	struct device		*dev;
+	struct irq_domain	*domain;
+};
+
+static void cpm_irq_process_irq_status(struct cpm_irq_info *info, u32 irq_number)
+{
+	int ret;
+
+	ret = generic_handle_domain_irq_safe(info->domain, irq_number);
+	if (ret)
+		dev_err(info->dev, "Error handling IRQ %u, error code %d\n", irq_number, ret);
+}
+
+static void cpm_irq_rx_callback(u32 context, const struct cpm_payload *msg, void *priv)
+{
+	struct cpm_irq_info *info = priv;
+	u32 irq_number = msg->payload[0];
+
+	cpm_irq_process_irq_status(info, irq_number);
+}
+
+static void cpm_irq_domain_remove_action(void *data)
+{
+	struct irq_domain *domain = data;
+	unsigned int hwirq;
+
+	for (hwirq = 0; hwirq < CPM_IRQ_COUNT; hwirq++)
+		irq_dispose_mapping(irq_find_mapping(domain, hwirq));
+
+	irq_domain_remove(domain);
+}
+
+static int cpm_irq_domain_map(struct irq_domain *d, unsigned int virq,
+			      irq_hw_number_t hwirq)
+{
+	struct cpm_irq_info *info = d->host_data;
+
+	if (hwirq >= CPM_IRQ_COUNT)
+		return -EINVAL;
+
+	irq_set_chip_data(virq, info);
+	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
+
+	dev_dbg(info->dev, "Mapped hwirq %lu to virq %u\n", hwirq, virq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops cpm_irq_domain_ops = {
+	.map = cpm_irq_domain_map,
+	.xlate = irq_domain_xlate_onecell,
+};
+
+static int cpm_irq_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id)
+{
+	struct device *dev = &adev->dev;
+	struct cpm_client *client;
+	struct cpm_irq_info *info;
+	int ret;
+
+	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	info->dev = dev;
+	auxiliary_set_drvdata(adev, info);
+
+	info->domain = irq_domain_create_tree(dev_fwnode(dev), &cpm_irq_domain_ops, info);
+	if (!info->domain) {
+		dev_err(dev, "Unable to add irq domain\n");
+		return -ENODEV;
+	}
+
+	ret = devm_add_action_or_reset(dev, cpm_irq_domain_remove_action, info->domain);
+	if (ret) {
+		dev_err(dev, "Failed to add irq domain removal action\n");
+		return ret;
+	}
+
+	client = devm_cpm_request_client(dev, CPM_AP_NS_IRQ_SERVICE,
+					 cpm_irq_rx_callback, info);
+	if (IS_ERR(client))
+		return dev_err_probe(dev, PTR_ERR(client),
+				     "Failed to request cpm client\n");
+
+	return 0;
+}
+
+static const struct auxiliary_device_id cpm_irq_id_table[] = {
+	{ .name = "google_cpm_mba.irq" },
+	{ },
+};
+MODULE_DEVICE_TABLE(auxiliary, cpm_irq_id_table);
+
+static struct auxiliary_driver cpm_irq_driver = {
+	.probe = cpm_irq_probe,
+	.name = "cpm-irq-chip",
+	.id_table = cpm_irq_id_table,
+};
+
+module_auxiliary_driver(cpm_irq_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Google CPM IRQ chip driver");
+MODULE_AUTHOR("Jim Wylder <jwylder@google.com>");
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 8/9] arm64: dts: google: Add Laguna CPM node
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (6 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 13:28   ` Krzysztof Kozlowski
  2026-10-26 21:43 ` [PATCH 9/9] MAINTAINERS: Add entries for CPM drivers used on Tensor SoCs Brian Norris
  2026-10-28 13:24 ` [PATCH 0/9] google: Add support for Laguna CPM Krzysztof Kozlowski
  9 siblings, 1 reply; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Central Power Manager (CPM) is a co-processor on the Google Tensor
SoCs, as used on Pixel phones. CPM supports power-management and a
number of other features.

The Application Processor communicates with CPM over a pair of mailbox
channels. CPM provides clock, power-domain, reset, and interrupt
services, so we include the appropriate cell-definitions for those.

Because this also adds clock, reset, and power-domain support, we add
them to the UART node at the same time.

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 arch/arm64/boot/dts/google/lga.dtsi | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/arch/arm64/boot/dts/google/lga.dtsi b/arch/arm64/boot/dts/google/lga.dtsi
index 6ceb9ea6a100..83b2361c5f00 100644
--- a/arch/arm64/boot/dts/google/lga.dtsi
+++ b/arch/arm64/boot/dts/google/lga.dtsi
@@ -7,8 +7,13 @@
 
 /dts-v1/;
 
+#include <dt-bindings/clock/google,lga-cpm.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
+#include <dt-bindings/interrupt-controller/irq-cpm-google.h>
 #include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/power/google,lga-cpm.h>
+#include <dt-bindings/reset/google,lga-cpm-reset.h>
+#include <dt-bindings/soc/google,lga-lpcm.h>
 
 / {
 	#address-cells = <2>;
@@ -356,6 +361,16 @@ abl@be000000 {
 		};
 	};
 
+	cpm: cpm {
+		compatible = "google,lga-cpm";
+		#clock-cells = <2>;
+		#power-domain-cells = <1>;
+		#reset-cells = <2>;
+		interrupt-controller;
+		#interrupt-cells = <1>;
+		mboxes = <&cpm_ap_ns_req_mba>, <&cpm_ap_ns_resp_mba>;
+	};
+
 	soc: soc@0 {
 		compatible = "simple-bus";
 		ranges = <0x0 0x0 0x0 0x0 0x10 0x0>;
@@ -423,9 +438,14 @@ lsion_cli16_uart: serial@db62000 {
 			reg = <0x0 0x0db62000 0x0 0x100>;
 
 			clock-frequency = <200000000>;
+			clocks = <&cpm LGA_LPCM_LSIO_N LSION_CLI16_UART_CLK>,
+				 <&cpm LGA_LPCM_LSIO_N LSION_CLI16_CFG_CLK>;
+			clock-names = "baudclk", "apb_pclk";
 			interrupts = <GIC_SPI 688 IRQ_TYPE_LEVEL_HIGH 0>;
+			power-domains = <&cpm LGA_PD_LSIO_N_CLI_GPIO>;
 			reg-io-width = <4>;
 			reg-shift = <2>;
+			resets = <&cpm LGA_LPCM_LSIO_N LSION_CLI16_UART_IC_CFG_RST>;
 
 			status = "disabled";
 		};
-- 
2.56.0.307.g5fe7bd680c-goog


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

* [PATCH 9/9] MAINTAINERS: Add entries for CPM drivers used on Tensor SoCs
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (7 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 8/9] arm64: dts: google: Add Laguna CPM node Brian Norris
@ 2026-10-26 21:43 ` Brian Norris
  2026-10-28 13:24 ` [PATCH 0/9] google: Add support for Laguna CPM Krzysztof Kozlowski
  9 siblings, 0 replies; 18+ messages in thread
From: Brian Norris @ 2026-10-26 21:43 UTC (permalink / raw)
  To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

The Google Tensor SoC known as Laguna introduces a variety of new
drivers, with Google-designed IP. Add entries to the GOOGLE TENSOR SoC
SUPPORT entry to cover the new drivers that interface with the Central
Power Manager (CPM).

Signed-off-by: Brian Norris <briannorris@chromium.org>
---

 MAINTAINERS | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/MAINTAINERS b/MAINTAINERS
index 20bd4e03bb54..c997d23e377d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11424,18 +11424,28 @@ C:	irc://irc.oftc.net/pixel6-kernel-dev
 F:	Documentation/devicetree/bindings/clock/google,gs101-clock.yaml
 F:	Documentation/devicetree/bindings/mailbox/google,lga-mailbox-array.yaml
 F:	Documentation/devicetree/bindings/phy/google,lga-usb-phy.yaml
-F:	Documentation/devicetree/bindings/soc/google/google,gs101-pmu-intr-gen.yaml
+F:	Documentation/devicetree/bindings/soc/google/
 F:	Documentation/devicetree/bindings/usb/google,lga-dwc3.yaml
 F:	arch/arm64/boot/dts/exynos/google/
 F:	arch/arm64/boot/dts/google/
+F:	drivers/clk/google/
 F:	drivers/clk/samsung/clk-gs101.c
+F:	drivers/irqchip/irq-google-*
 F:	drivers/mailbox/goog-mba*
 F:	drivers/phy/phy-google-usb.c
+F:	drivers/pmdomain/google/
+F:	drivers/reset/google/
+F:	drivers/soc/google/
 F:	drivers/soc/samsung/gs101-pmu.c
 F:	drivers/phy/samsung/phy-gs101-ufs.c
 F:	drivers/usb/dwc3/dwc3-google.c
-F:	include/dt-bindings/clock/google,gs101*
+F:	include/dt-bindings/clock/google,*
+F:	include/dt-bindings/interrupt-controller/irq-cpm-google.h
+F:	include/dt-bindings/power/google,*
+F:	include/dt-bindings/reset/google,*
+F:	include/dt-bindings/soc/google,*
 F:	include/linux/mailbox/goog-mba-message.h
+F:	include/soc/google/
 K:	[gG]oogle.?[tT]ensor
 
 GPD FAN DRIVER
-- 
2.56.0.307.g5fe7bd680c-goog


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

* Re: [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array
  2026-10-26 21:43 ` [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array Brian Norris
@ 2026-10-28 13:10   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-28 13:10 UTC (permalink / raw)
  To: Brian Norris
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel, Peter Griffin, devicetree, Tudor Ambarus,
	André Draszik, linux-samsung-soc, linux-pm, linux-kernel,
	Radu Rendec, linux-arm-kernel, Mariano Marciello,
	Douglas Anderson, linux-clk, Jakub Głowacki

On Mon, Oct 26, 2026 at 02:43:39PM -0700, Brian Norris wrote:
> These are the request and response channels used for communicating with
> the Central Power Manager (CPM) found on the Google Tensor SoC known as
> Laguna.
> 
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
> Based on WIP bindings from:
> 
>   https://lore.kernel.org/all/20261023165211.v2.3.Ia186a29119b4b5c409815b755e0c2c3965b0495f@changeid/
>   [PATCH v2 3/4] dt-bindings: mailbox: goog-mba: Add goog-mba mailbox bindings
> 
> which is part of this series:
> 
>   https://lore.kernel.org/all/20261023235408.3063350-1-dianders@chromium.org/
>   [PATCH v2 0/4] mailbox: Improve the mbox core then introduce the goog-mba driver
> 
> The binding is still under review, but I include this anyway to help
> prove out a working solution. This patch should not be accepted until
> the binding is settled.

Regardless, DTS goes to the end of patchset (see submitting patches in
DT bindings dir).

Best regards,
Krzysztof


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

* Re: [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding
  2026-10-26 21:43 ` [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding Brian Norris
@ 2026-10-28 13:19   ` Krzysztof Kozlowski
  2026-10-28 13:19   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-28 13:19 UTC (permalink / raw)
  To: Brian Norris
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel, Peter Griffin, devicetree, Tudor Ambarus,
	André Draszik, linux-samsung-soc, linux-pm, linux-kernel,
	Radu Rendec, linux-arm-kernel, Mariano Marciello,
	Douglas Anderson, linux-clk, Jakub Głowacki

On Mon, Oct 26, 2026 at 02:43:40PM -0700, Brian Norris wrote:
>  .../bindings/soc/google/google,lga-cpm.yaml   |  74 +++++++
>  include/dt-bindings/clock/google,lga-cpm.h    | 191 ++++++++++++++++++
>  .../interrupt-controller/irq-cpm-google.h     |  16 ++
>  include/dt-bindings/power/google,lga-cpm.h    |  79 ++++++++
>  .../dt-bindings/reset/google,lga-cpm-reset.h  | 175 ++++++++++++++++
>  include/dt-bindings/soc/google,lga-lpcm.h     |  54 +++++
>  6 files changed, 589 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml
>  create mode 100644 include/dt-bindings/clock/google,lga-cpm.h
>  create mode 100644 include/dt-bindings/interrupt-controller/irq-cpm-google.h
>  create mode 100644 include/dt-bindings/power/google,lga-cpm.h
>  create mode 100644 include/dt-bindings/reset/google,lga-cpm-reset.h
>  create mode 100644 include/dt-bindings/soc/google,lga-lpcm.h
> 
> diff --git a/Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml b/Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml
> new file mode 100644
> index 000000000000..546e6c7fff80
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/soc/google/google,lga-cpm.yaml

Place it in power/ rather.

> @@ -0,0 +1,74 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/soc/google/google,lga-cpm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Google Central Power Manager (CPM)
> +
> +maintainers:
> +  - Brian Norris <briannorris@chromium.org>
> +
> +description: |

Do not need '|' unless you need to preserve formatting.

> +  The Central Power Manager (CPM) is an on-chip power management subsystem
> +  found on Google Tensor SoCs. It has a variety of responsibilities, from DVFS
> +  and power management to resets, PMIC control, and even RTC management. The
> +  Application Processor communicates with CPM over a set of mailbox channels.
> +
> +properties:
> +  compatible:
> +    const: google,lga-cpm
> +
> +  mboxes:
> +    items:
> +      - description: TX mailbox channel for sending requests to CPM
> +      - description: RX mailbox channel for receiving responses from CPM
> +
> +  '#power-domain-cells':
> +    const: 1
> +    description: See <dt-bindings/power/google,lga-cpm.h> for power domain IDs.
> +
> +  '#reset-cells':
> +    const: 2
> +    description:
> +      Each reset is specified by an LPCM ID (cell 1) and a reset ID (cell 2).
> +      See <dt-bindings/soc/google,lga-lpcm.h> for LPCM IDs and
> +      <dt-bindings/reset/google,lga-cpm-reset.h> for reset IDs.
> +
> +  interrupt-controller: true
> +
> +  '#interrupt-cells':
> +    const: 1
> +    description:
> +      Interrupt identifiers are assigned by CPM firmware. See
> +      <dt-bindings/interrupt-controller/irq-cpm-google.h> for interrupt IDs.
> +
> +  '#clock-cells':
> +    const: 2
> +    description:
> +      Each clock is specified by an LPCM ID (cell 1) and a clock ID (cell
> +      2). See <dt-bindings/soc/google,lga-lpcm.h> for LPCM IDs and
> +      <dt-bindings/clock/google,lga-cpm.h> for clock IDs.
> +
> +required:
> +  - compatible
> +  - mboxes
> +  - '#clock-cells'
> +  - '#power-domain-cells'
> +  - '#reset-cells'
> +  - '#interrupt-cells'
> +  - interrupt-controller

Keep same order of properties here and in properties list, something
resembling alphabetical sorting without leading '#'.

> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    cpm: cpm {

Drop unused label.

Node names should be generic. See also an explanation and list of
examples (not exhaustive) in DT specification:
https://devicetree-specification.readthedocs.io/en/latest/chapter2-devicetree-basics.html#generic-names-recommendation
If you cannot find a name matching your device, please check in kernel
sources for similar cases or you can grow the spec (via pull request to
DT spec repo).
Most likely: power-controller if has power domains. power-management
otherwise.


> +        compatible = "google,lga-cpm";
> +        mboxes = <&cpm_ap_ns_req_mba>, <&cpm_ap_ns_resp_mba>;
> +        #clock-cells = <2>;
> +        #power-domain-cells = <1>;
> +        #reset-cells = <2>;
> +        #interrupt-cells = <1>;
> +        interrupt-controller;
> +    };
> diff --git a/include/dt-bindings/clock/google,lga-cpm.h b/include/dt-bindings/clock/google,lga-cpm.h
> new file mode 100644
> index 000000000000..6d5a5567226d
> --- /dev/null
> +++ b/include/dt-bindings/clock/google,lga-cpm.h
> @@ -0,0 +1,191 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +
> +#ifndef __DT_BINDINGS_CLOCK_GOOGLE_LGA_CPM_H
> +#define __DT_BINDINGS_CLOCK_GOOGLE_LGA_CPM_H
> +
> +/*
> + * LPCM clock ID's of each SSWRPs
> + */
> +
> +#define LSIOS_GPIO_CLK			0
> +#define LSIOS_MCLK0_CLK			1
> +#define LSIOS_PWM_CLK			2
> +#define LSIOS_PWM_CFG_CLK		3
> +#define LSIOS_CLI0_CFG_CLK		4
> +#define LSIOS_CLI1_CFG_CLK		5
> +#define LSIOS_CLI2_CFG_CLK		6
> +#define LSIOS_CLI3_CFG_CLK		7
> +#define LSIOS_CLI4_CFG_CLK		8
> +#define LSIOS_CLI5_CFG_CLK		9
> +#define LSIOS_CLI6_CFG_CLK		10
> +#define LSIOS_CLI0_SPI_CLK		11
> +#define LSIOS_CLI1_SPI_CLK		12
> +#define LSIOS_CLI2_SPI_CLK		13
> +#define LSIOS_CLI3_SPI_CLK		14
> +#define LSIOS_CLI4_SPI_CLK		15
> +#define LSIOS_CLI5_SPI_CLK		16
> +#define LSIOS_CLI6_SPI_CLK		17
> +#define LSIOS_CLI0_I2C_CLK		18
> +#define LSIOS_CLI1_I2C_CLK		19
> +#define LSIOS_CLI2_I2C_CLK		20
> +#define LSIOS_CLI3_I2C_CLK		21
> +#define LSIOS_CLI4_I2C_CLK		22
> +#define LSIOS_CLI5_I2C_CLK		23
> +#define LSIOS_CLI6_I2C_CLK		24
> +#define LSIOS_CLI0_UART_CLK		25
> +#define LSIOS_CLI1_UART_CLK		26
> +#define LSIOS_CLI2_UART_CLK		27
> +#define LSIOS_CLI3_UART_CLK		28
> +#define LSIOS_CLI4_UART_CLK		29
> +#define LSIOS_CLI5_UART_CLK		30
> +#define LSIOS_CLI6_UART_CLK		31
> +#define LSIOS_CLI0_I3C_CLK		32
> +#define LSIOS_CLI1_I3C_CLK		33
> +#define LSIOS_CLI2_I3C_CLK		34
> +#define LSIOS_CLI3_I3C_CLK		35
> +#define LSIOS_CLI4_I3C_CLK		36
> +#define LSIOS_CLI5_I3C_CLK		37
> +#define LSIOS_CLI6_I3C_CLK		38
> +#define LSIOS_MCLK1_CLK			39
> +#define LSIOS_MCLK2_CLK			40
> +#define LSIOS_MCLK3_CLK			41
> +#define LSIOS_MCLK4_CLK			42
> +#define LSIOS_MCLK5_CLK			43
> +#define LSIOS_MCLK6_CLK			44
> +#define LSIOS_MCLK7_CLK			45
> +
> +#define LSION_GPIO_CLK			0
> +#define LSION_CLI_INT0_I2C_CLK		1
> +#define LSION_CLI_INT0_I3C_CLK		2
> +#define LSION_CLI_INT0_UART_CLK		3
> +#define LSION_CLI_INT0_SPI_CLK		4
> +#define LSION_CLI_INT0_CFG_CLK		5
> +#define LSION_CLI_INT1_I2C_CLK		6
> +#define LSION_CLI_INT1_I3C_CLK		7
> +#define LSION_CLI_INT1_UART_CLK		8
> +#define LSION_CLI_INT1_SPI_CLK		9
> +#define LSION_CLI_INT1_CFG_CLK		10
> +#define LSION_CLI_INT2_I2C_CLK		11
> +#define LSION_CLI_INT2_I3C_CLK		12
> +#define LSION_CLI_INT2_UART_CLK		13
> +#define LSION_CLI_INT2_SPI_CLK		14
> +#define LSION_CLI_INT2_CFG_CLK		15
> +#define LSION_CLI_INT3_I2C_CLK		16
> +#define LSION_CLI_INT3_I3C_CLK		17
> +#define LSION_CLI_INT3_UART_CLK		18
> +#define LSION_CLI_INT3_SPI_CLK		19
> +#define LSION_CLI_INT3_CFG_CLK		20
> +#define LSION_CLI_INT4_I2C_CLK		21
> +#define LSION_CLI_INT4_I3C_CLK		22
> +#define LSION_CLI_INT4_UART_CLK		23
> +#define LSION_CLI_INT4_SPI_CLK		24
> +#define LSION_CLI_INT4_CFG_CLK		25
> +#define LSION_CLI15_CFG_CLK		26
> +#define LSION_CLI16_CFG_CLK		27
> +#define LSION_CLI15_I2C_CLK		28
> +#define LSION_CLI16_I2C_CLK		29
> +#define LSION_CLI15_SPI_CLK		30
> +#define LSION_CLI16_SPI_CLK		31
> +#define LSION_CLI15_UART_CLK		32
> +#define LSION_CLI16_UART_CLK		33
> +#define LSION_CLI15_I3C_CLK		34
> +#define LSION_CLI16_I3C_CLK		35
> +
> +#define LSIOE_GPIO_CLK			0
> +#define LSIOE_CLI7_CFG_CLK		1
> +#define LSIOE_CLI8_CFG_CLK		2
> +#define LSIOE_CLI9_CFG_CLK		3
> +#define LSIOE_CLI10_CFG_CLK		4
> +#define LSIOE_CLI11_CFG_CLK		5
> +#define LSIOE_CLI12_CFG_CLK		6
> +#define LSIOE_CLI13_CFG_CLK		7
> +#define LSIOE_CLI14_CFG_CLK		8
> +#define LSIOE_CLI7_SPI_CLK		9
> +#define LSIOE_CLI8_SPI_CLK		10
> +#define LSIOE_CLI9_SPI_CLK		11
> +#define LSIOE_CLI10_SPI_CLK		12
> +#define LSIOE_CLI11_SPI_CLK		13
> +#define LSIOE_CLI12_SPI_CLK		14
> +#define LSIOE_CLI13_SPI_CLK		15
> +#define LSIOE_CLI14_SPI_CLK		16
> +#define LSIOE_CLI7_I2C_CLK		17
> +#define LSIOE_CLI8_I2C_CLK		18
> +#define LSIOE_CLI9_I2C_CLK		19
> +#define LSIOE_CLI10_I2C_CLK		20
> +#define LSIOE_CLI11_I2C_CLK		21
> +#define LSIOE_CLI12_I2C_CLK		22
> +#define LSIOE_CLI13_I2C_CLK		23
> +#define LSIOE_CLI14_I2C_CLK		24
> +#define LSIOE_CLI7_UART_CLK		25
> +#define LSIOE_CLI8_UART_CLK		26
> +#define LSIOE_CLI9_UART_CLK		27
> +#define LSIOE_CLI10_UART_CLK		28
> +#define LSIOE_CLI11_UART_CLK		29
> +#define LSIOE_CLI12_UART_CLK		30
> +#define LSIOE_CLI13_UART_CLK		31
> +#define LSIOE_CLI14_UART_CLK		32
> +#define LSIOE_SPMI_CLK			33
> +#define LSIOE_CLI7_I3C_CLK		34
> +#define LSIOE_CLI8_I3C_CLK		35
> +#define LSIOE_CLI9_I3C_CLK		36
> +#define LSIOE_CLI10_I3C_CLK		37
> +#define LSIOE_CLI11_I3C_CLK		38
> +#define LSIOE_CLI12_I3C_CLK		39
> +#define LSIOE_CLI13_I3C_CLK		40
> +#define LSIOE_CLI14_I3C_CLK		41
> +
> +#define HSIOS_MPHY_REF_OSC_CLK		0
> +#define HSIOS_MPHY_REF_TCX0_CLK		1
> +#define HSIOS_GPIO_REF_OSC_CLK		2
> +#define HSIOS_GPIO_REF_TCX0_CLK		3
> +#define HSIOS_HCLK_CLK			4
> +#define HSIOS_SYS_CLK			5
> +#define HSIOS_T_CLK			6
> +#define HSIOS_LP_T_CLK			7
> +#define HSIOS_MPHY_CFG_CLK		8
> +#define HSIOS_UFS_CFG_CLK		9
> +#define HSIOS_PHY_FW_CLK		10
> +#define HSIOS_PCIE_REF_CLK		11
> +#define HSIOS_UFS_RESET_CLK		12
> +#define HSIOS_UFS_LP_RESET_CLK		13
> +#define HSIOS_UFS_ARESET_CLK		14
> +#define HSIOS_UFS_PHY_RESET_CLK		15
> +#define HSIOS_AUX_CLK			16
> +
> +#define HSION_USB_CLK			0
> +#define HSION_DP0_CLK			1
> +#define HSION_DP1_CLK			2
> +#define HSION_PHY_FW_CLK		3
> +#define HSION_PCIE_REF_CLK		4
> +#define HSION_U2PHY_REF_CLK		5
> +#define HSION_USBDP_SUSPEND_CLK		6
> +#define HSION_USBC_NON_STICKY_CLK	7
> +#define HSION_USBC_STICKY_CLK		8
> +#define HSION_USB_DRD_BUS_CLK		9
> +#define HSION_CSR_CLK			10
> +#define HSION_USB_TOP_CSR_CLK		11
> +#define HSION_DP_PCLK_CLK		12
> +#define HSION_DP_CFG_CLK		13
> +#define HSION_U2PHY_APB_CLK		14
> +#define HSION_DP_VCC_CLK		15
> +#define HSION_U3PHY_REF_CLK		16
> +#define HSION_USB2_PHY_RESET_CLK	17
> +#define HSION_USB3DP_PHY_RESET_CLK	18
> +#define HSION_EBU_CLK			19
> +
> +#define DPU_DP0_PIX_CLK			0
> +#define DPU_DP1_PIX_CLK			1
> +#define DPU_DSI0_PIX_CLK		2
> +#define DPU_DSI1_PIX_CLK		3
> +#define DPU_DSI1_PHY_CLK		4
> +#define DPU_FE0_CLK			5
> +#define DPU_FE1_CLK			6
> +#define DPU_BE_CLK			7
> +#define DPU_AON_CLK			8
> +
> +#define GPCM_DPU_PLL_DP_CLK		0
> +#define GPCM_HSION_PLL_DP_CLK		1
> +
> +#define CPU_SSWRP_TSS_CLK		1
> +
> +#endif /* __DT_BINDINGS_CLOCK_GOOGLE_LGA_CPM_H */
> diff --git a/include/dt-bindings/interrupt-controller/irq-cpm-google.h b/include/dt-bindings/interrupt-controller/irq-cpm-google.h
> new file mode 100644
> index 000000000000..26d83bfe39af
> --- /dev/null
> +++ b/include/dt-bindings/interrupt-controller/irq-cpm-google.h
> @@ -0,0 +1,16 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * This header provides constants for CPM IRQ.
> + *
> + * Copyright 2025-2026 Google LLC
> + *
> + */
> +#ifndef _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_CPM_H
> +#define _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_CPM_H
> +
> +#define CPM_IRQ_RTC_ALARM_NO			0

Keep naming consistent. Clocks has _CLK suffix, so this should not use
prefix.


> +#define CPM_IRQ_AOC_MIN				7

Anyway, ABI constants should be indexed from 0 and incremented by 1.

> +#define CPM_IRQ_AOC_MAX				156
> +#define CPM_IRQ_AP_GDMC_WAKE			157
> +
> +#endif  /* _DT_BINDINGS_INTERRUPT_CONTROLLER_IRQ_CPM_H */
> diff --git a/include/dt-bindings/power/google,lga-cpm.h b/include/dt-bindings/power/google,lga-cpm.h
> new file mode 100644
> index 000000000000..4b35c59df2cc
> --- /dev/null
> +++ b/include/dt-bindings/power/google,lga-cpm.h
> @@ -0,0 +1,79 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +
> +#ifndef __DT_BINDINGS_POWER_GOOGLE_LGA_CPM_H
> +#define __DT_BINDINGS_POWER_GOOGLE_LGA_CPM_H
> +
> +/* AOC */
> +#define LGA_PD_SSWRP_AOC		0

And here LGA prefix... this feels random.

> +#define LGA_PD_AOC_CORE			1
> +
> +/* AURORA */
> +#define LGA_PD_SSWRP_AURORA		2
> +
> +/* CODEC_3P */
> +#define LGA_PD_SSWRP_CODEC_3P		3
> +
> +/* DPU */
> +#define LGA_PD_DPU_DP			4
> +#define LGA_PD_SSWRP_DPU		5
> +#define LGA_PD_DPU_FE0			6
> +#define LGA_PD_DPU_FE1			7
> +#define LGA_PD_DPU_DSI0			8
> +#define LGA_PD_DPU_DSI1			9
> +
> +/* EH */
> +#define LGA_PD_SSWRP_EH			10
> +
> +/* G2D */
> +#define LGA_PD_SSWRP_G2D		11
> +#define LGA_PD_G2D_CORE			12
> +
> +/* GPDMA */
> +#define LGA_PD_SSWRP_GPDMA		13
> +
> +/* GPU */
> +#define LGA_PD_SSWRP_GPU		14
> +
> +/* GSW */
> +#define LGA_PD_SSWRP_GSW		15
> +
> +/* HSIO_N */
> +#define LGA_PD_SSWRP_HSIO_N		16
> +#define LGA_PD_HSIO_N_USB		17
> +#define LGA_PD_HSIO_N_USB_PSW		18
> +#define LGA_PD_HSIO_N_PCIE		19
> +#define LGA_PD_HSIO_N_DP		20
> +#define LGA_PD_HSIO_N_EBU		21
> +#define LGA_PD_HSIO_N_EBU_CORE		22
> +
> +/* HSIO_S */
> +#define LGA_PD_SSWRP_HSIO_S		23
> +#define LGA_PD_HSIO_S_UFS_PREP		24
> +#define LGA_PD_HSIO_S_UFS		25
> +#define LGA_PD_HSIO_S_PCIE		26
> +#define LGA_PD_HSIO_S_SD		27
> +
> +/* ISPBE */
> +#define LGA_PD_SSWRP_ISPBE		28
> +
> +/* ISPFE */
> +#define LGA_PD_SSWRP_ISPFE		29
> +#define LGA_PD_ISPFE_PIPE		30
> +#define LGA_PD_ISPFE_CSIS		31
> +
> +/* LSIO_E */
> +#define LGA_PD_SSWRP_LSIO_E		32
> +#define LGA_PD_LSIO_E_CLI_GPIO		33
> +
> +/* LSIO_N */
> +#define LGA_PD_SSWRP_LSIO_N		34
> +#define LGA_PD_LSIO_N_CLI_GPIO		35
> +
> +/* LSIO_S */
> +#define LGA_PD_SSWRP_LSIO_S		36
> +#define LGA_PD_LSIO_S_CLI_GPIO		37
> +
> +/* TPU */
> +#define LGA_PD_SSWRP_TPU		38
> +
> +#endif /* __DT_BINDINGS_POWER_GOOGLE_LGA_CPM_H */
> diff --git a/include/dt-bindings/reset/google,lga-cpm-reset.h b/include/dt-bindings/reset/google,lga-cpm-reset.h
> new file mode 100644
> index 000000000000..d147296dbffc
> --- /dev/null
> +++ b/include/dt-bindings/reset/google,lga-cpm-reset.h

Filename: google,lga-cpm.h

Keep things consistent. All the future contributors will duplicate every
issue you make here and then claim in discussion "but I saw existing
code like that".



> @@ -0,0 +1,175 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +
> +#ifndef __DT_BINDINGS_RESET_GOOGLE_LGA_CPM_RESET_H
> +#define __DT_BINDINGS_RESET_GOOGLE_LGA_CPM_RESET_H
> +
> +/* HSION */
> +#define HSION_PCIE_PERST_RST			0
> +#define HSION_PCIE_APP_HOLD_PHY_RST		1
> +#define HSION_PCIE_INIT_RST			2
> +#define HSION_PCIE_PWR_UP_RST			3
> +#define HSION_USB_DP_CFG_RST			4
> +#define HSION_DP_PCLK_RST			5
> +#define HSION_USB_TOP_CSR_RST			6
> +#define HSION_PIXEL_CK0_RST			7
> +#define HSION_PIXEL_CK1_RST			8
> +#define HSION_U2PHY_APB_RST			9
> +#define HSION_USBC_NON_STICKY_RST		10
> +#define HSION_USBC_STICKY_RST			11
> +#define HSION_USB_DRD_BUS_RST			12
> +#define HSION_USB2_PHY_RESET			13
> +#define HSION_USB3DP_PHY_RESET			14
> +#define HSION_EBU_RST				15
> +
> +/* HSIOS */
> +#define HSIOS_PCIE_PERST_RST			0
> +#define HSIOS_PCIE_APP_HOLD_PHY_RST		1
> +#define HSIOS_PCIE_PWR_UP_RST			2
> +#define HSIOS_UFS_PHY_RST			3
> +#define HSIOS_PCIE_INIT_RST			4
> +
> +/* LSIOS */
> +#define LSIOS_CLI0_I2C_IC_CFG_RST		0
> +#define LSIOS_CLI0_I3C_IC_CFG_RST		1
> +#define LSIOS_CLI0_UART_IC_CFG_RST		2
> +#define LSIOS_CLI0_SPI_IC_CFG_RST		3
> +#define LSIOS_CLI1_I2C_IC_CFG_RST		4
> +#define LSIOS_CLI1_I3C_IC_CFG_RST		5
> +#define LSIOS_CLI1_UART_IC_CFG_RST		6
> +#define LSIOS_CLI1_SPI_IC_CFG_RST		7
> +#define LSIOS_CLI2_I2C_IC_CFG_RST		8
> +#define LSIOS_CLI2_I3C_IC_CFG_RST		9
> +#define LSIOS_CLI2_UART_IC_CFG_RST		10
> +#define LSIOS_CLI2_SPI_IC_CFG_RST		11
> +#define LSIOS_CLI3_I2C_IC_CFG_RST		12
> +#define LSIOS_CLI3_I3C_IC_CFG_RST		13
> +#define LSIOS_CLI3_UART_IC_CFG_RST		14
> +#define LSIOS_CLI3_SPI_IC_CFG_RST		15
> +#define LSIOS_CLI4_I2C_IC_CFG_RST		16
> +#define LSIOS_CLI4_I3C_IC_CFG_RST		17
> +#define LSIOS_CLI4_UART_IC_CFG_RST		18
> +#define LSIOS_CLI4_SPI_IC_CFG_RST		19
> +#define LSIOS_CLI5_I2C_IC_CFG_RST		20
> +#define LSIOS_CLI5_I3C_IC_CFG_RST		21
> +#define LSIOS_CLI5_UART_IC_CFG_RST		22
> +#define LSIOS_CLI5_SPI_IC_CFG_RST		23
> +#define LSIOS_CLI6_I2C_IC_CFG_RST		24
> +#define LSIOS_CLI6_I3C_IC_CFG_RST		25
> +#define LSIOS_CLI6_UART_IC_CFG_RST		26
> +#define LSIOS_CLI6_SPI_IC_CFG_RST		27
> +#define LSIOS_CLI0_SPI_IC_RST			28
> +#define LSIOS_CLI1_SPI_IC_RST			29
> +#define LSIOS_CLI2_SPI_IC_RST			30
> +#define LSIOS_CLI3_SPI_IC_RST			31
> +#define LSIOS_CLI4_SPI_IC_RST			32
> +#define LSIOS_CLI5_SPI_IC_RST			33
> +#define LSIOS_CLI6_SPI_IC_RST			34
> +#define LSIOS_CLI0_SPI_CFG_RST			35
> +#define LSIOS_CLI1_SPI_CFG_RST			36
> +#define LSIOS_CLI2_SPI_CFG_RST			37
> +#define LSIOS_CLI3_SPI_CFG_RST			38
> +#define LSIOS_CLI4_SPI_CFG_RST			39
> +#define LSIOS_CLI5_SPI_CFG_RST			40
> +#define LSIOS_CLI6_SPI_CFG_RST			41
> +#define LSIOS_PWM_IC_RST			42
> +#define LSIOS_PWM_CFG_RST			43
> +#define LSIOS_GPIO_IC_RST			44
> +
> +/* LSION */
> +#define LSION_CLI15_I2C_IC_CFG_RST		0
> +#define LSION_CLI15_I3C_IC_CFG_RST		1
> +#define LSION_CLI15_UART_IC_CFG_RST		2
> +#define LSION_CLI15_SPI_IC_CFG_RST		3
> +#define LSION_CLI16_I2C_IC_CFG_RST		4
> +#define LSION_CLI16_I3C_IC_CFG_RST		5
> +#define LSION_CLI16_UART_IC_CFG_RST		6
> +#define LSION_CLI16_SPI_IC_CFG_RST		7
> +#define LSION_CLI17_INT0_I2C_IC_CFG_RST		8
> +#define LSION_CLI17_INT0_I3C_IC_CFG_RST		9
> +#define LSION_CLI17_INT0_UART_IC_CFG_RST	10
> +#define LSION_CLI17_INT0_SPI_IC_CFG_RST		11
> +#define LSION_CLI18_INT1_I2C_IC_CFG_RST		12
> +#define LSION_CLI18_INT1_I3C_IC_CFG_RST		13
> +#define LSION_CLI18_INT1_UART_IC_CFG_RST	14
> +#define LSION_CLI18_INT1_SPI_IC_CFG_RST		15
> +#define LSION_CLI19_INT2_I2C_IC_CFG_RST		16
> +#define LSION_CLI19_INT2_I3C_IC_CFG_RST		17
> +#define LSION_CLI19_INT2_UART_IC_CFG_RST	18
> +#define LSION_CLI19_INT2_SPI_IC_CFG_RST		19
> +#define LSION_CLI20_INT3_I2C_IC_CFG_RST		20
> +#define LSION_CLI20_INT3_I3C_IC_CFG_RST		21
> +#define LSION_CLI20_INT3_UART_IC_CFG_RST	22
> +#define LSION_CLI20_INT3_SPI_IC_CFG_RST		23
> +#define LSION_CLI21_INT4_I2C_IC_CFG_RST		24
> +#define LSION_CLI21_INT4_I3C_IC_CFG_RST		25
> +#define LSION_CLI21_INT4_UART_IC_CFG_RST	26
> +#define LSION_CLI21_INT4_SPI_IC_CFG_RST		27
> +#define LSION_CLI15_SPI_IC_RST			28
> +#define LSION_CLI16_SPI_IC_RST			29
> +#define LSION_CLI17_INT0_SPI_IC_RST		30
> +#define LSION_CLI18_INT1_SPI_IC_RST		31
> +#define LSION_CLI19_INT2_SPI_IC_RST		32
> +#define LSION_CLI20_INT3_SPI_IC_RST		33
> +#define LSION_CLI21_INT4_SPI_IC_RST		34
> +#define LSION_CLI15_SPI_CFG_RST			35
> +#define LSION_CLI16_SPI_CFG_RST			36
> +#define LSION_CLI17_INT0_SPI_CFG_RST		37
> +#define LSION_CLI18_INT1_SPI_CFG_RST		38
> +#define LSION_CLI19_INT2_SPI_CFG_RST		39
> +#define LSION_CLI20_INT3_SPI_CFG_RST		40
> +#define LSION_CLI21_INT4_SPI_CFG_RST		41
> +#define LSION_GPIO_IC_RST			42
> +
> +/* LSIOE */
> +#define LSIOE_CLI7_I2C_IC_CFG_RST		0
> +#define LSIOE_CLI7_I3C_IC_CFG_RST		1
> +#define LSIOE_CLI7_UART_IC_CFG_RST		2
> +#define LSIOE_CLI7_SPI_IC_CFG_RST		3
> +#define LSIOE_CLI8_I2C_IC_CFG_RST		4
> +#define LSIOE_CLI8_I3C_IC_CFG_RST		5
> +#define LSIOE_CLI8_UART_IC_CFG_RST		6
> +#define LSIOE_CLI8_SPI_IC_CFG_RST		7
> +#define LSIOE_CLI9_I2C_IC_CFG_RST		8
> +#define LSIOE_CLI9_I3C_IC_CFG_RST		9
> +#define LSIOE_CLI9_UART_IC_CFG_RST		10
> +#define LSIOE_CLI9_SPI_IC_CFG_RST		11
> +#define LSIOE_CLI10_I2C_IC_CFG_RST		12
> +#define LSIOE_CLI10_I3C_IC_CFG_RST		13
> +#define LSIOE_CLI10_UART_IC_CFG_RST		14
> +#define LSIOE_CLI10_SPI_IC_CFG_RST		15
> +#define LSIOE_CLI11_I2C_IC_CFG_RST		16
> +#define LSIOE_CLI11_I3C_IC_CFG_RST		17
> +#define LSIOE_CLI11_UART_IC_CFG_RST		18
> +#define LSIOE_CLI11_SPI_IC_CFG_RST		19
> +#define LSIOE_CLI12_I2C_IC_CFG_RST		20
> +#define LSIOE_CLI12_I3C_IC_CFG_RST		21
> +#define LSIOE_CLI12_UART_IC_CFG_RST		22
> +#define LSIOE_CLI12_SPI_IC_CFG_RST		23
> +#define LSIOE_CLI13_I2C_IC_CFG_RST		24
> +#define LSIOE_CLI13_I3C_IC_CFG_RST		25
> +#define LSIOE_CLI13_UART_IC_CFG_RST		26
> +#define LSIOE_CLI13_SPI_IC_CFG_RST		27
> +#define LSIOE_CLI14_I2C_IC_CFG_RST		28
> +#define LSIOE_CLI14_I3C_IC_CFG_RST		29
> +#define LSIOE_CLI14_UART_IC_CFG_RST		30
> +#define LSIOE_CLI14_SPI_IC_CFG_RST		31
> +#define LSIOE_CLI7_SPI_IC_RST			32
> +#define LSIOE_CLI8_SPI_IC_RST			33
> +#define LSIOE_CLI9_SPI_IC_RST			34
> +#define LSIOE_CLI10_SPI_IC_RST			35
> +#define LSIOE_CLI11_SPI_IC_RST			36
> +#define LSIOE_CLI12_SPI_IC_RST			37
> +#define LSIOE_CLI13_SPI_IC_RST			38
> +#define LSIOE_CLI14_SPI_IC_RST			39
> +#define LSIOE_CLI7_SPI_CFG_RST			40
> +#define LSIOE_CLI8_SPI_CFG_RST			41
> +#define LSIOE_CLI9_SPI_CFG_RST			42
> +#define LSIOE_CLI10_SPI_CFG_RST			43
> +#define LSIOE_CLI11_SPI_CFG_RST			44
> +#define LSIOE_CLI12_SPI_CFG_RST			45
> +#define LSIOE_CLI13_SPI_CFG_RST			46
> +#define LSIOE_CLI14_SPI_CFG_RST			47
> +#define LSIOE_GPIO_IC_RST			48
> +#define LSIOE_SPMI_RST				49
> +
> +#endif /* __DT_BINDINGS_RESET_GOOGLE_LGA_CPM_RESET_H */
> diff --git a/include/dt-bindings/soc/google,lga-lpcm.h b/include/dt-bindings/soc/google,lga-lpcm.h
> new file mode 100644
> index 000000000000..d1944afff2b9
> --- /dev/null
> +++ b/include/dt-bindings/soc/google,lga-lpcm.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +
> +#ifndef __DT_BINDINGS_SOC_GOOGLE_LGA_LPCM_H
> +#define __DT_BINDINGS_SOC_GOOGLE_LGA_LPCM_H
> +
> +/*
> + * LPCM IDs of each SSWRP.
> + */
> +#define LGA_LPCM_CPM			0

What are all these?

Best regards,
Krzysztof


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

* Re: [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding
  2026-10-26 21:43 ` [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding Brian Norris
  2026-10-28 13:19   ` Krzysztof Kozlowski
@ 2026-10-28 13:19   ` Krzysztof Kozlowski
  1 sibling, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-28 13:19 UTC (permalink / raw)
  To: Brian Norris
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel, Peter Griffin, devicetree, Tudor Ambarus,
	André Draszik, linux-samsung-soc, linux-pm, linux-kernel,
	Radu Rendec, linux-arm-kernel, Mariano Marciello,
	Douglas Anderson, linux-clk, Jakub Głowacki

On Mon, Oct 26, 2026 at 02:43:40PM -0700, Brian Norris wrote:
> The Central Power Manager (CPM) is a co-processor on the Google Tensor
> SoCs, as used on Pixel phones. CPM supports power-management and a
> number of other features.
> 

Ah, and one more:

A nit, subject: drop second/last, redundant "binding". The
"dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v7.1-rc7/source/Documentation/devicetree/bindings/submitting-patches.rst#L23


Best regards,
Krzysztof


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

* Re: [PATCH 0/9] google: Add support for Laguna CPM
  2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
                   ` (8 preceding siblings ...)
  2026-10-26 21:43 ` [PATCH 9/9] MAINTAINERS: Add entries for CPM drivers used on Tensor SoCs Brian Norris
@ 2026-10-28 13:24 ` Krzysztof Kozlowski
  9 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-28 13:24 UTC (permalink / raw)
  To: Brian Norris
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel, Peter Griffin, devicetree, Tudor Ambarus,
	André Draszik, linux-samsung-soc, linux-pm, linux-kernel,
	Radu Rendec, linux-arm-kernel, Mariano Marciello,
	Douglas Anderson, linux-clk, Jakub Głowacki

On Mon, Oct 26, 2026 at 02:43:38PM -0700, Brian Norris wrote:
> The Central Power Manager (CPM) is a co-processor on the Google Tensor
> SoCs, as used on Pixel phones. CPM supports power-management and a
> number of other features.
> 
> The Application Processor communicates with CPM over a pair of mailbox
> channels. CPM provides clock, power-domain, reset, and interrupt
> services.
> 
> This series adds device tree bindings, DTS, and drivers to support CPM,
> and several of the basic services provided by CPM. These services are a
> minimal set needed to bootstrap many other components on the SoC --
> e.g., I have a working proof-of-concept PCIe driver running on top of
> this series + pinctrl (not included here). CPM provides some other
> components not yet included in this series (e.g., PMIC, RTC).
> 
> This series depends on the bindings and mailbox-payload-definitions from
> this series:
> 
>   https://lore.kernel.org/all/20261023235408.3063350-1-dianders@chromium.org/
>   [PATCH v2 0/4] mailbox: Improve the mbox core then introduce the goog-mba driver
> 
> So this series cannot be applied as-is yet. However, v1 of that series
> was under review for about 3 months, so I'd like to at least:
> 
>   1) demonstrate its clients (this came up at
>      https://lore.kernel.org/all/CAD=FV=VW2h1zWDYWQ6LknX3O+3=EY665jde35Ly+UQzuscXy=Q@mail.gmail.com/,
>      for one); and
> 
>   2) start review for anything that isn't significantly impacted by
>      potential changes in the above mailbox series.
> 
> With luck, the general design and DT bindings don't need to change, but
> even if so, I expect any changes should not significantly affect:
> 
>  (a) the CPM DT binding or DTS (apart from #mbox-cells)
> 
>  (b) the CPM interface (cpm_request_client(), cpm_send_message())
> 
>  (c) any of the CPM clients (irqchip, reset, clk, pmdomain)
> 
> Thus, I think this falls somewhere between "PATCH" and "RFC".

Either you expect review or not. RFC means no. If that's patch, then at
the top level of cover letter, as the most important thing, you explain
the dependency and merging proposal of this. You combined here like 5
different subsystens...

Best regards,
Krzysztof


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

* Re: [PATCH 3/9] soc: google: Add CPM over MailBox Array driver
  2026-10-26 21:43 ` [PATCH 3/9] soc: google: Add CPM over MailBox Array driver Brian Norris
@ 2026-10-28 13:27   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-28 13:27 UTC (permalink / raw)
  To: Brian Norris
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel, Peter Griffin, devicetree, Tudor Ambarus,
	André Draszik, linux-samsung-soc, linux-pm, linux-kernel,
	Radu Rendec, linux-arm-kernel, Mariano Marciello,
	Douglas Anderson, linux-clk, Jakub Głowacki

On Mon, Oct 26, 2026 at 02:43:41PM -0700, Brian Norris wrote:
> The Central Power Manager (CPM) is a co-processor on the Google Tensor
> SoCs, as used on Pixel phones. CPM supports power-management and a
> number of other features.
> 
> The Application Processor communicates with CPM over a pair of mailbox
> channels (the "MailBox Array" (MBA)). CPM provides clock, power-domain,
> reset, interrupt services, and more.
> 
> It's difficult to support all of these services within a single driver,
> in part because it's a lot of code to manage, but also because there can
> be complex relationships between the individual services. For example,
> clocks may have dependencies on power-domains. Instead, the CPM driver
> only handles some basic initialization, and otherwise creates child
> auxiliary_bus devices to handle each service. It also provides an API
> wrapper around the mailbox protocol, so clients don't have to worry
> about some of the details. This API assumes all users are children of
> the CPM device.
> 
> Implementation note about the "session" handling: the mailbox API
> doesn't provide any timeouts or guarantees about a response message, but
> CPM clients would like an (optional) timeout. So there's a little bit of
> complexity around reference-counting a "session" (i.e., outstanding
> request still waiting on a response), as that session may outlive the
> client's timeout.
> 
> Quick note about hardware queueing: this is implemented in our mailbox
> driver by allowing clients to request the "same" channel multiple times.
> A client is expected to keep requesting the channel until it sees
> -EBUSY, meaning we've requested all of them. We then utilize those
> channels in a round-robin fashion.
> 
> Because it may be useful both for reviewers familiar with the downstream
> Linux support for these devices, or for future developers looking at
> adapting/improving/adding further features later on, it's worth noting
> some major deviations from the downstream drivers:
> 
>  * This driver doesn't contain much of the queueing complexity from the
>    downstream driver. The mailbox driver was significantly rewritten
>    beneath us, to support most of the queueing there, and thus exposes a
>    simpler API to us.
> 
>  * No session pool; we just allocate sessions on-demand, enough to
>    handle timeouts while a response is still pending.
> 
>  * Use auxiliary_bus, with CPM services represented as child devices on
>    this bus. Downstream services tended to be a bunch of artificial
>    platform devices, with fake-mailbox phandles to the CPM "interface"
>    node. This is an atypical device tree structure, and most of those
>    platform devices came from device nodes that were less for describing
>    the hardware, and more for describing how we wanted software to look.
>    So far, I haven't found services that don't work as child auxiliary
>    devices. If we later have examples that need some combination of both
>    iomem resources and CPM resources (and so may need to live on the
>    platform bus), I'm sure we can add support for non-child (e.g.,
>    phandle) interface methods.
> 
>  * Add devm_* client helpers, to make it easier for clients to clean up
>    properly. (Hint: most downstream clients did not clean up properly.)
> 
>  * Reduce the surface area of exposed (EXPORTed, or header definitions)
>    protocol details, such as goog_mba_q_*() methods. Many such details
>    aren't used outside the CPM driver, so it's probably best to keep
>    them local until proven otherwise.
> 
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
> Based on a WIP mailbox API from:
> 
>   https://lore.kernel.org/all/20261023165211.v2.4.I5cef580a62c86ba6c3465ad336fa509afbdf1476@changeid/
>   [PATCH v2 4/4] mailbox: goog-mba: Introduce the goog-mba mailbox driver
> 
> which is part of this series:
> 
>   https://lore.kernel.org/all/20261023235408.3063350-1-dianders@chromium.org/
>   [PATCH v2 0/4] mailbox: Improve the mbox core then introduce the goog-mba driver
> 
> That API consists of:
> 
>  1) a header (<linux/mailbox/goog-mba-message.h>), which so far seems
>     typical and uncontroversial and
> 
>  2) a strategy for supporting hardware queueing -- where the current
>     proposal is described in the commit message. I suppose this strategy
>     could still change, although it was discussed at length in v1 of
>     that series.
> 
> Anyway, this patch should not be accepted until the above series is
> settled.
> 
>  drivers/soc/Kconfig                       |   1 +
>  drivers/soc/Makefile                      |   1 +
>  drivers/soc/google/Kconfig                |  13 +
>  drivers/soc/google/Makefile               |   3 +
>  drivers/soc/google/google-cpm-mba-trace.h | 214 +++++
>  drivers/soc/google/google-cpm-mba.c       | 964 ++++++++++++++++++++++
>  drivers/soc/google/google-cpm-mba.h       |  30 +
>  include/soc/google/goog_cpm_service_ids.h |  84 ++

Keep naming consistent. You use everywhere hyphens, so here as well.

>  include/soc/google/google-cpm-mba.h       | 147 ++++


>  9 files changed, 1457 insertions(+)
>  create mode 100644 drivers/soc/google/Kconfig
>  create mode 100644 drivers/soc/google/Makefile
>  create mode 100644 drivers/soc/google/google-cpm-mba-trace.h
>  create mode 100644 drivers/soc/google/google-cpm-mba.c
>  create mode 100644 drivers/soc/google/google-cpm-mba.h
>  create mode 100644 include/soc/google/goog_cpm_service_ids.h
>  create mode 100644 include/soc/google/google-cpm-mba.h
> 
> diff --git a/drivers/soc/Kconfig b/drivers/soc/Kconfig
> index 483cbdcae153..fbe6460424c7 100644
> --- a/drivers/soc/Kconfig
> +++ b/drivers/soc/Kconfig
> @@ -10,6 +10,7 @@ source "drivers/soc/canaan/Kconfig"
>  source "drivers/soc/cirrus/Kconfig"
>  source "drivers/soc/fsl/Kconfig"
>  source "drivers/soc/fujitsu/Kconfig"
> +source "drivers/soc/google/Kconfig"
>  source "drivers/soc/hisilicon/Kconfig"
>  source "drivers/soc/imx/Kconfig"
>  source "drivers/soc/ixp4xx/Kconfig"
> diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile
> index 5a3efd68355c..6241e543dad7 100644
> --- a/drivers/soc/Makefile
> +++ b/drivers/soc/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_MACH_DOVE)		+= dove/
>  obj-y				+= fsl/
>  obj-y				+= fujitsu/
>  obj-$(CONFIG_ARCH_GEMINI)	+= gemini/
> +obj-y				+= google/
>  obj-y				+= hisilicon/
>  obj-y				+= imx/
>  obj-y				+= ixp4xx/
> diff --git a/drivers/soc/google/Kconfig b/drivers/soc/google/Kconfig
> new file mode 100644
> index 000000000000..9cd04be63b23
> --- /dev/null
> +++ b/drivers/soc/google/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +config GOOGLE_CPM_MBA
> +	tristate "Google Tensor CPM MailBox Array Interface"
> +	depends on ARCH_GOOGLE || COMPILE_TEST
> +	depends on MAILBOX

Can it be compile tested without mailbox?

Best regards,
Krzysztof


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

* Re: [PATCH 8/9] arm64: dts: google: Add Laguna CPM node
  2026-10-26 21:43 ` [PATCH 8/9] arm64: dts: google: Add Laguna CPM node Brian Norris
@ 2026-10-28 13:28   ` Krzysztof Kozlowski
  0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-28 13:28 UTC (permalink / raw)
  To: Brian Norris
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Stephen Boyd,
	Brian Masney, Jerome Brunet, Thomas Gleixner, Ulf Hansson,
	Philipp Zabel, Peter Griffin, devicetree, Tudor Ambarus,
	André Draszik, linux-samsung-soc, linux-pm, linux-kernel,
	Radu Rendec, linux-arm-kernel, Mariano Marciello,
	Douglas Anderson, linux-clk, Jakub Głowacki

On Mon, Oct 26, 2026 at 02:43:46PM -0700, Brian Norris wrote:
> The Central Power Manager (CPM) is a co-processor on the Google Tensor
> SoCs, as used on Pixel phones. CPM supports power-management and a
> number of other features.
> 
> The Application Processor communicates with CPM over a pair of mailbox
> channels. CPM provides clock, power-domain, reset, and interrupt
> services, so we include the appropriate cell-definitions for those.
> 
> Because this also adds clock, reset, and power-domain support, we add
> them to the UART node at the same time.
> 
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
> 
>  arch/arm64/boot/dts/google/lga.dtsi | 20 ++++++++++++++++++++
>  1 file changed, 20 insertions(+)
> 
> diff --git a/arch/arm64/boot/dts/google/lga.dtsi b/arch/arm64/boot/dts/google/lga.dtsi
> index 6ceb9ea6a100..83b2361c5f00 100644
> --- a/arch/arm64/boot/dts/google/lga.dtsi
> +++ b/arch/arm64/boot/dts/google/lga.dtsi
> @@ -7,8 +7,13 @@
>  
>  /dts-v1/;
>  
> +#include <dt-bindings/clock/google,lga-cpm.h>
>  #include <dt-bindings/interrupt-controller/arm-gic.h>
> +#include <dt-bindings/interrupt-controller/irq-cpm-google.h>
>  #include <dt-bindings/interrupt-controller/irq.h>
> +#include <dt-bindings/power/google,lga-cpm.h>
> +#include <dt-bindings/reset/google,lga-cpm-reset.h>
> +#include <dt-bindings/soc/google,lga-lpcm.h>
>  
>  / {
>  	#address-cells = <2>;
> @@ -356,6 +361,16 @@ abl@be000000 {
>  		};
>  	};
>  
> +	cpm: cpm {

Name should be rather: power-controller

Best regards,
Krzysztof


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

* Re: [PATCH 6/9] reset: google: Add CPM reset driver
  2026-10-26 21:43 ` [PATCH 6/9] reset: google: Add CPM reset driver Brian Norris
@ 2026-10-28 14:03   ` Philipp Zabel
  0 siblings, 0 replies; 18+ messages in thread
From: Philipp Zabel @ 2026-10-28 14:03 UTC (permalink / raw)
  To: Brian Norris, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Stephen Boyd, Brian Masney, Jerome Brunet, Thomas Gleixner,
	Ulf Hansson
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki

On Mo, 2026-10-26 at 14:43 -0700, Brian Norris wrote:
> Google Tensor SoCs (used on Pixel phones) manage many resources
> (including resets) using a Central Power Manager (CPM) co-processor. We
> communicate with CPM using mailboxes, abstracted in the
> cpm_request_client(), etc., API.
> 
> For posterity, I list a summary of changes made relative to the
> downstream Pixel driver:
> 
> * Switch to auxiliary bus, as a child of a CPM device
> 
> * Use devm more heavily
> 
> * Avoid representing each reset as its own Device Tree node. Use a
>   limited per-SoC table to describe any additional per-SoC details
>   instead.
> 
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
> 
>  drivers/reset/Kconfig            |   1 +
>  drivers/reset/Makefile           |   1 +
>  drivers/reset/google/Kconfig     |  11 +
>  drivers/reset/google/Makefile    |   2 +
>  drivers/reset/google/reset-cpm.c | 367 +++++++++++++++++++++++++++++++
>  5 files changed, 382 insertions(+)
>  create mode 100644 drivers/reset/google/Kconfig
>  create mode 100644 drivers/reset/google/Makefile
>  create mode 100644 drivers/reset/google/reset-cpm.c
> 
> diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
> index eda9c698a95f..37dbddcd620f 100644
> --- a/drivers/reset/Kconfig
> +++ b/drivers/reset/Kconfig
> @@ -451,6 +451,7 @@ config RESET_ZYNQMP
>  	  This enables the reset controller driver for Xilinx ZynqMP SoCs.
>  
>  source "drivers/reset/amlogic/Kconfig"
> +source "drivers/reset/google/Kconfig"
>  source "drivers/reset/hisilicon/Kconfig"
>  source "drivers/reset/spacemit/Kconfig"
>  source "drivers/reset/starfive/Kconfig"
> diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
> index 489fafd6bbe3..9754c274766c 100644
> --- a/drivers/reset/Makefile
> +++ b/drivers/reset/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-y += core.o
>  obj-y += amlogic/
> +obj-y += google/
>  obj-y += hisilicon/
>  obj-y += spacemit/
>  obj-y += starfive/
> diff --git a/drivers/reset/google/Kconfig b/drivers/reset/google/Kconfig
> new file mode 100644
> index 000000000000..38431b4f9001
> --- /dev/null
> +++ b/drivers/reset/google/Kconfig
> @@ -0,0 +1,11 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config RESET_CPM
> +	tristate "Google CPM Reset Controller"
> +	depends on ARCH_GOOGLE || COMPILE_TEST
> +	depends on GOOGLE_CPM_MBA
> +	help
> +	  Driver support for reset controllers found on Google Tensor SoCs,
> +	  operating over the CPM mailbox interface. This allows peripheral
> +	  drivers to assert and deassert resets on LPCM hardware blocks.
> +
> +	  If unsure, say N.
> diff --git a/drivers/reset/google/Makefile b/drivers/reset/google/Makefile
> new file mode 100644
> index 000000000000..437c19459464
> --- /dev/null
> +++ b/drivers/reset/google/Makefile
> @@ -0,0 +1,2 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +obj-$(CONFIG_RESET_CPM) += reset-cpm.o
> diff --git a/drivers/reset/google/reset-cpm.c b/drivers/reset/google/reset-cpm.c
> new file mode 100644
> index 000000000000..5f144f1ad5f2
> --- /dev/null
> +++ b/drivers/reset/google/reset-cpm.c
> @@ -0,0 +1,367 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Device driver to control Google SoC LPCM resets via CPM.
> + * Copyright 2023-2026 Google LLC
> + */
> +
> +#include <dt-bindings/soc/google,lga-lpcm.h>
> +#include <linux/auxiliary_bus.h>
> +#include <linux/bitfield.h>
> +#include <linux/bits.h>
> +#include <linux/container_of.h>
> +#include <linux/debugfs.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/reset-controller.h>
> +#include <soc/google/goog_cpm_service_ids.h>
> +#include <soc/google/google-cpm-mba.h>
> +
> +#define MAILBOX_SEND_TIMEOUT_MS		3000
> +#define LPCM_CMD_SET_RST		0x4
> +
> +#define OP_ID_BITFIELD			GENMASK(31, 24)
> +#define RST_ID_BITFIELD			GENMASK(23, 16)
> +#define LPCM_ID_BITFIELD		GENMASK(15, 8)
> +#define REQ_ID_BITFIELD			GENMASK(7, 0)
> +
> +#define ASSERT_OP_ID			0
> +#define DEASSERT_OP_ID			1
> +
> +#define NO_ERROR			0
> +#define ERR_LPCM_NOT_SUPPORTED		(-24)
> +#define ERR_LPCM_INVALID_ARGS		(-8)
> +#define ERR_LPCM_TIMED_OUT		(-13)
> +#define ERR_LPCM_GENERIC		(-1)
> +
> +#define CPM_RST_LPCM_ID_MASK		GENMASK(31, 16)
> +#define CPM_RST_ID_MASK			GENMASK(15, 0)
> +
> +static inline unsigned long to_rstc_idx(u32 lpcm_id, u32 rst_id)
> +{
> +	return FIELD_PREP(CPM_RST_LPCM_ID_MASK, lpcm_id) |
> +	       FIELD_PREP(CPM_RST_ID_MASK, rst_id);
> +}
> +
> +static inline u32 lpcm_from_idx(unsigned long idx)
> +{
> +	return FIELD_GET(CPM_RST_LPCM_ID_MASK, idx);
> +}
> +
> +static inline u32 rst_from_idx(unsigned long idx)
> +{
> +	return FIELD_GET(CPM_RST_ID_MASK, idx);
> +}
> +
> +struct cpm_reset_data {
> +	u32 lpcm_id;
> +	u32 nr_resets;
> +	const char *name;
> +};
> +
> +struct cpm_reset_soc_data {
> +	const struct cpm_reset_data *resets;
> +	size_t num_resets;
> +};
> +
> +struct goog_cpm_rst_provider {
> +	struct device *dev;
> +	struct reset_controller_dev rcdev;
> +	struct cpm_client *cpm_client;
> +	struct dentry *debugfs_root;
> +	struct goog_cpm_rst_debugfs_entry *dbg_entries;
> +	const struct cpm_reset_soc_data *soc_data;
> +};
> +
> +#define to_provider(rc) container_of(rc, struct goog_cpm_rst_provider, rcdev)

This should be an inline function.

> +struct goog_cpm_rst_debugfs_entry {
> +	struct goog_cpm_rst_provider *provider;
> +	u32 lpcm_id;
> +};
> +
> +static int goog_cpm_rst_mba_check_ret(struct device *dev, int ret)
> +{
> +	switch (ret) {
> +	case NO_ERROR:
> +		return 0;
> +	case ERR_LPCM_NOT_SUPPORTED:
> +		dev_err(dev, "the received request_id is invalid\n");
> +		return -EPROTO;

-EOPNOTSUPP?

> +	case ERR_LPCM_INVALID_ARGS:
> +		dev_err(dev, "invalid lpcm_id or rst_id\n");
> +		return -EINVAL;
> +	case ERR_LPCM_TIMED_OUT:
> +		dev_err(dev, "operation timed out\n");
> +		return -ETIMEDOUT;
> +	case ERR_LPCM_GENERIC:
> +		dev_err(dev, "internal error occurred\n");
> +		return -EREMOTEIO;
> +	default:
> +		dev_err(dev, "unknown error response from CPM\n");
> +		return -EPROTO;
> +	}
> +}

Is this specific to reset functionality, or should this maybe live in
CPM common code? I see that the return values in the power-domain
driver are not translated, but the clock driver contains a near-
identical copy of this function. Or maybe just printing the translated
error with "%pe" is good enough?

> +static int goog_cpm_rst_mba_resp_hdl(struct device *dev, const struct cpm_request *cpm_req)
> +{
> +	int dst = cpm_req->dst_id;
> +	int ret = cpm_req->resp_msg->payload[0];
> +
> +	dev_dbg(dev, "got resp from %u, data %d.\n", dst, ret);
> +
> +	return goog_cpm_rst_mba_check_ret(dev, ret);
> +}
> +
> +static int goog_cpm_rst_send_mba_mail(struct goog_cpm_rst_provider *provider,
> +				      u32 lpcm_id, unsigned long rst_id, int op_id)
> +{
> +	struct device *dev = provider->dev;
> +	struct cpm_payload resp_msg;
> +	struct cpm_payload req_msg = {};
> +	struct cpm_request cpm_req = {
> +		.msg_type = REQUEST_MSG,
> +		.req_msg = &req_msg,
> +		.resp_msg = &resp_msg,
> +		.dst_id = CPM_COMMON_LPCM_SERVICE,
> +		.tout_ms = MAILBOX_SEND_TIMEOUT_MS,
> +	};
> +	int ret;
> +
> +	req_msg.payload[0] = FIELD_PREP(REQ_ID_BITFIELD, LPCM_CMD_SET_RST) |
> +			     FIELD_PREP(LPCM_ID_BITFIELD, lpcm_id) |
> +			     FIELD_PREP(RST_ID_BITFIELD, rst_id) |
> +			     FIELD_PREP(OP_ID_BITFIELD, op_id);
> +
> +	dev_dbg(dev, "lpcm_id %d, rst_id %lu: send mba mail: [%u, %u, %u]\n",
> +		lpcm_id, rst_id, req_msg.payload[0], req_msg.payload[1],
> +		req_msg.payload[2]);
> +
> +	ret = cpm_send_message(provider->cpm_client, &cpm_req);
> +	if (ret < 0) {
> +		dev_err(dev,
> +			"lpcm_id %d, rst_id %lu: failed to send request to CPM, ret=%d\n",
> +			lpcm_id, rst_id, ret);
> +		return ret;
> +	}
> +
> +	return goog_cpm_rst_mba_resp_hdl(dev, &cpm_req);
> +}
> +
> +static int goog_cpm_rst_assert(struct reset_controller_dev *rcdev,
> +			       unsigned long idx)
> +{
> +	struct goog_cpm_rst_provider *provider = to_provider(rcdev);
> +	u32 lpcm_id = lpcm_from_idx(idx);
> +	u32 rst_id = rst_from_idx(idx);
> +
> +	dev_dbg(rcdev->dev, "assert reset id=%u of lpcm %u\n", rst_id, lpcm_id);
> +	return goog_cpm_rst_send_mba_mail(provider, lpcm_id, rst_id, ASSERT_OP_ID);
> +}
> +
> +static int goog_cpm_rst_deassert(struct reset_controller_dev *rcdev,
> +				 unsigned long idx)
> +{
> +	struct goog_cpm_rst_provider *provider = to_provider(rcdev);
> +	u32 lpcm_id = lpcm_from_idx(idx);
> +	u32 rst_id = rst_from_idx(idx);
> +
> +	dev_dbg(rcdev->dev, "deassert reset id=%u of lpcm %u\n", rst_id, lpcm_id);

This all could be deduplicated by moving it into the _send_mba_mail()
function, using str_assert_deassert(op_id == ASSERT_OP_ID).

> +	return goog_cpm_rst_send_mba_mail(provider, lpcm_id, rst_id, DEASSERT_OP_ID);
> +}
> +
> +static const struct reset_control_ops goog_cpm_rst_ops = {
> +	.assert = goog_cpm_rst_assert,
> +	.deassert = goog_cpm_rst_deassert,
> +};
> +
> +static int goog_cpm_rst_debugfs_assert_set(void *data, u64 rst_id)
> +{
> +	struct goog_cpm_rst_debugfs_entry *entry = data;
> +	unsigned long idx = to_rstc_idx(entry->lpcm_id, rst_id);
> +
> +	return goog_cpm_rst_assert(&entry->provider->rcdev, idx);

Calling into reset_control_ops like this bypasses all reference
counting in the core and breaks reset controller API promises.

> +}
> +
> +static int goog_cpm_rst_debugfs_deassert_set(void *data, u64 rst_id)
> +{
> +	struct goog_cpm_rst_debugfs_entry *entry = data;
> +	unsigned long idx = to_rstc_idx(entry->lpcm_id, rst_id);
> +
> +	return goog_cpm_rst_deassert(&entry->provider->rcdev, idx);
> +}
> +
> +DEFINE_DEBUGFS_ATTRIBUTE(goog_cpm_rst_assert_debugfs_fops, NULL,
> +			 goog_cpm_rst_debugfs_assert_set, "%llu\n");
> +
> +DEFINE_DEBUGFS_ATTRIBUTE(goog_cpm_rst_deassert_debugfs_fops, NULL,
> +			 goog_cpm_rst_debugfs_deassert_set, "%llu\n");
> +
> +static void goog_cpm_rst_add_debugfs(struct goog_cpm_rst_debugfs_entry *entry,
> +				     struct dentry *debugfs_root,
> +				     const char *name)
> +{
> +	struct dentry *d = debugfs_create_dir(name, debugfs_root);
> +
> +	debugfs_create_file("assert", 0220, d, entry,
> +			    &goog_cpm_rst_assert_debugfs_fops);
> +	debugfs_create_file("deassert", 0220, d, entry,
> +			    &goog_cpm_rst_deassert_debugfs_fops);
> +}

Please drop the debugfs functionality or explain what it is needed for.

> +static int goog_cpm_rst_get_nr_resets(const struct cpm_reset_soc_data *soc_data, u32 lpcm_id)
> +{
> +	int i;
> +
> +	for (i = 0; i < soc_data->num_resets; i++)
> +		if (soc_data->resets[i].lpcm_id == lpcm_id)
> +			return soc_data->resets[i].nr_resets;
> +
> +	return -EINVAL;
> +}
> +
> +static int goog_cpm_rst_of_xlate(struct reset_controller_dev *rcdev,
> +				 const struct of_phandle_args *reset_spec)
> +{
> +	struct goog_cpm_rst_provider *provider = to_provider(rcdev);
> +	u32 lpcm_id, rst_id;
> +	int max_resets;
> +
> +	if (reset_spec->args_count != 2)
> +		return -EINVAL;
> +
> +	lpcm_id = reset_spec->args[0];
> +	rst_id = reset_spec->args[1];
> +
> +	max_resets = goog_cpm_rst_get_nr_resets(provider->soc_data, lpcm_id);
> +	if (max_resets < 0)
> +		return max_resets;
> +
> +	if (rst_id >= max_resets)
> +		return -EINVAL;
> +
> +	return to_rstc_idx(lpcm_id, rst_id);
> +}
> +
> +static const struct cpm_reset_data cpm_resets_lga_entries[] = {
> +	{
> +		.lpcm_id = LGA_LPCM_HSIO_N,
> +		.nr_resets = 16,
> +		.name = "hsio-n",
> +	},
> +	{
> +		.lpcm_id = LGA_LPCM_HSIO_S,
> +		.nr_resets = 5,
> +		.name = "hsio-s",
> +	},
> +	{
> +		.lpcm_id = LGA_LPCM_LSIO_S,
> +		.nr_resets = 45,
> +		.name = "lsio-s",
> +	},
> +	{
> +		.lpcm_id = LGA_LPCM_LSIO_N,
> +		.nr_resets = 43,
> +		.name = "lsio-n",
> +	},
> +	{
> +		.lpcm_id = LGA_LPCM_LSIO_E,
> +		.nr_resets = 50,
> +		.name = "lsio-e",
> +	},
> +};
> +
> +static const struct cpm_reset_soc_data cpm_resets_lga = {
> +	.resets = cpm_resets_lga_entries,
> +	.num_resets = ARRAY_SIZE(cpm_resets_lga_entries),
> +};
> +
> +static const struct of_device_id cpm_rst_of_match[] = {
> +	{ .compatible = "google,lga-cpm", .data = &cpm_resets_lga },
> +	{ /* sentinel */ }

Please choose one style (align with goog_cpm_rst_id_table).

> +};
> +
> +static int goog_cpm_rst_probe(struct auxiliary_device *auxdev,
> +			      const struct auxiliary_device_id *id)
> +{
> +	struct device *dev = &auxdev->dev;
> +	struct goog_cpm_rst_provider *provider;
> +	const struct of_device_id *match;
> +	int i, ret;
> +
> +	match = of_match_node(cpm_rst_of_match, dev->of_node);
> +	if (!match) {
> +		dev_err(dev, "no matching SoC data found\n");

This should never happen, does it warrant an error message?

> +		return -ENODEV;
> +	}
> +
> +	provider = devm_kzalloc(dev, sizeof(*provider), GFP_KERNEL);
> +	if (!provider)
> +		return -ENOMEM;
> +
> +	provider->dev = dev;

This is not needed, you can just use provider->rcdev.dev.

> +	provider->soc_data = match->data;
> +
> +	provider->cpm_client = devm_cpm_request_client(dev, 0, NULL, NULL);
> +	if (IS_ERR(provider->cpm_client))
> +		return dev_err_probe(dev, PTR_ERR(provider->cpm_client),
> +				     "Failed to request cpm client\n");
> +	auxiliary_set_drvdata(auxdev, provider);
> +
> +	provider->rcdev.dev = dev;
> +	provider->rcdev.owner = THIS_MODULE;
> +	provider->rcdev.of_node = dev->of_node;
> +	provider->rcdev.ops = &goog_cpm_rst_ops;
> +	provider->rcdev.of_reset_n_cells = 2;
> +	provider->rcdev.of_xlate = goog_cpm_rst_of_xlate;
> +
> +	ret = devm_reset_controller_register(dev, &provider->rcdev);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to register reset controller: %d\n", ret);
> +		return ret;
> +	}
> +
> +	provider->debugfs_root = debugfs_create_dir(dev_name(dev), NULL);
> +	if (!IS_ERR(provider->debugfs_root)) {
> +		provider->dbg_entries = devm_kcalloc(dev, provider->soc_data->num_resets,
> +						     sizeof(*provider->dbg_entries), GFP_KERNEL);
> +		if (!provider->dbg_entries) {
> +			debugfs_remove_recursive(provider->debugfs_root);
> +			return -ENOMEM;
> +		}
> +
> +		for (i = 0; i < provider->soc_data->num_resets; i++) {
> +			provider->dbg_entries[i].provider = provider;
> +			provider->dbg_entries[i].lpcm_id = provider->soc_data->resets[i].lpcm_id;
> +			goog_cpm_rst_add_debugfs(&provider->dbg_entries[i],
> +						 provider->debugfs_root,
> +						 provider->soc_data->resets[i].name);
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static void goog_cpm_rst_remove(struct auxiliary_device *auxdev)
> +{
> +	struct goog_cpm_rst_provider *provider = auxiliary_get_drvdata(auxdev);
> +
> +	debugfs_remove_recursive(provider->debugfs_root);
> +}
> +
> +static const struct auxiliary_device_id goog_cpm_rst_id_table[] = {
> +	{ .name = "google_cpm_mba.reset" },
> +	{},

Please choose one style (align with cpm_rst_of_match).

> +};
> +MODULE_DEVICE_TABLE(auxiliary, goog_cpm_rst_id_table);
> +
> +static struct auxiliary_driver goog_cpm_rst_driver = {
> +	.probe = goog_cpm_rst_probe,
> +	.remove = goog_cpm_rst_remove,
> +	.name = "google-reset-cpm",
> +	.id_table = goog_cpm_rst_id_table,
> +};
> +
> +module_auxiliary_driver(goog_cpm_rst_driver);
> +
> +MODULE_DESCRIPTION("Google CPM Reset Driver");
> +MODULE_LICENSE("GPL");

regards
Philipp

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

* Re: [PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver
  2026-10-26 21:43 ` [PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver Brian Norris
@ 2026-10-28 20:23   ` Thomas Gleixner
  0 siblings, 0 replies; 18+ messages in thread
From: Thomas Gleixner @ 2026-10-28 20:23 UTC (permalink / raw)
  To: Brian Norris, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Stephen Boyd, Brian Masney, Jerome Brunet, Ulf Hansson,
	Philipp Zabel
  Cc: Peter Griffin, devicetree, Tudor Ambarus, André Draszik,
	linux-samsung-soc, linux-pm, linux-kernel, Radu Rendec,
	linux-arm-kernel, Mariano Marciello, Douglas Anderson, linux-clk,
	Jakub Głowacki, Brian Norris

On Mon, Oct 26 2026 at 14:43, Brian Norris wrote:
> +
> +static void cpm_irq_process_irq_status(struct cpm_irq_info *info, u32 irq_number)
> +{
> +	int ret;
> +
> +	ret = generic_handle_domain_irq_safe(info->domain, irq_number);

  int ret = generic...();

> +	if (ret)
> +		dev_err(info->dev, "Error handling IRQ %u, error code %d\n", irq_number, ret);
> +}
> +

> +static const struct irq_domain_ops cpm_irq_domain_ops = {
> +	.map = cpm_irq_domain_map,
> +	.xlate = irq_domain_xlate_onecell,

Tabular layout please

> +};
> +
> +static int cpm_irq_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id)
> +{
> +	struct device *dev = &adev->dev;
> +	struct cpm_client *client;
> +	struct cpm_irq_info *info;
> +	int ret;
> +
> +	info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> +	if (!info)
> +		return -ENOMEM;
> +
> +	info->dev = dev;
> +	auxiliary_set_drvdata(adev, info);
> +
> +	info->domain = irq_domain_create_tree(dev_fwnode(dev), &cpm_irq_domain_ops, info);
> +	if (!info->domain) {
> +		dev_err(dev, "Unable to add irq domain\n");
> +		return -ENODEV;
> +	}
> +
> +	ret = devm_add_action_or_reset(dev, cpm_irq_domain_remove_action, info->domain);
> +	if (ret) {
> +		dev_err(dev, "Failed to add irq domain removal action\n");
> +		return ret;
> +	}
> +
> +	client = devm_cpm_request_client(dev, CPM_AP_NS_IRQ_SERVICE,
> +					 cpm_irq_rx_callback, info);
> +	if (IS_ERR(client))
> +		return dev_err_probe(dev, PTR_ERR(client),
> +				     "Failed to request cpm client\n");

No line break required. You have 100 characters.

> +static struct auxiliary_driver cpm_irq_driver = {
> +	.probe = cpm_irq_probe,
> +	.name = "cpm-irq-chip",
> +	.id_table = cpm_irq_id_table,

Tabular layout

> +};
> +

Pointless new line.

> +module_auxiliary_driver(cpm_irq_driver);

Other than those nitpicks this looks reasonable.

> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Google CPM IRQ chip driver");
> +MODULE_AUTHOR("Jim Wylder <jwylder@google.com>");

This does not add up: I can't find Jim in the From/SOB tags of the mail.

Thanks,

        tglx

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

end of thread, other threads:[~2026-10-28 20:23 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-26 21:43 [PATCH 0/9] google: Add support for Laguna CPM Brian Norris
2026-10-26 21:43 ` [PATCH 1/9] arm64: dts: google: Add Laguna CPM mailbox array Brian Norris
2026-10-28 13:10   ` Krzysztof Kozlowski
2026-10-26 21:43 ` [PATCH 2/9] dt-bindings: soc: google: Add Google Tensor CPM binding Brian Norris
2026-10-28 13:19   ` Krzysztof Kozlowski
2026-10-28 13:19   ` Krzysztof Kozlowski
2026-10-26 21:43 ` [PATCH 3/9] soc: google: Add CPM over MailBox Array driver Brian Norris
2026-10-28 13:27   ` Krzysztof Kozlowski
2026-10-26 21:43 ` [PATCH 4/9] pmdomain: google: Add CPM power-domain driver Brian Norris
2026-10-26 21:43 ` [PATCH 5/9] clk: google: Add CPM clock driver Brian Norris
2026-10-26 21:43 ` [PATCH 6/9] reset: google: Add CPM reset driver Brian Norris
2026-10-28 14:03   ` Philipp Zabel
2026-10-26 21:43 ` [PATCH 7/9] irqchip/google-cpm: Add CPM IRQ driver Brian Norris
2026-10-28 20:23   ` Thomas Gleixner
2026-10-26 21:43 ` [PATCH 8/9] arm64: dts: google: Add Laguna CPM node Brian Norris
2026-10-28 13:28   ` Krzysztof Kozlowski
2026-10-26 21:43 ` [PATCH 9/9] MAINTAINERS: Add entries for CPM drivers used on Tensor SoCs Brian Norris
2026-10-28 13:24 ` [PATCH 0/9] google: Add support for Laguna CPM Krzysztof Kozlowski

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

all inboxes | Powered by JetHome®