Linux Power Management development
 help / color / mirror / Atom feed
* [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver
@ 2026-05-30 14:00 Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
                   ` (5 more replies)
  0 siblings, 6 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
  To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
	linux-arm-msm, linux-kernel, linux-pm, Rob Herring

Hi all,

This series adds a Qualcomm interconnect provider for the MSM8x60
family of SoCs (MSM8260/MSM8660/APQ8060), modelling the four fabrics
that connect masters and slaves on these Scorpion-class SoCs:

  - AFAB  : Application/CPU fabric
  - SFAB  : System fabric (peripherals, USB, SDCC, etc.)
  - MMFAB : Multimedia fabric (MDP, VFE, VIDC, GPU, JPEG, VPE, ROT)
  - DFAB  : Daytona fabric (low-bandwidth peripherals)

The driver implements the interconnect-provider API so that consumer
drivers (display, camera, video, GPU, USB, MMC) can request bandwidth
between specific masters and slaves via icc_set_bw(), letting the
firmware-managed bus-scaling logic decide actual NoC clock rates.

Used on the HP TouchPad (Tenderloin) and other early Scorpion-class
form-factors; without it, the multimedia and storage paths are starved
of bandwidth and run at minimum NoC clocks.

Thanks,
Herman

Herman van Hazendonk (2):
  dt-bindings: interconnect: qcom: add msm8660 fabric IDs
  interconnect: qcom: add MSM8x60 NoC driver

 drivers/interconnect/qcom/Kconfig             |   10 +
 drivers/interconnect/qcom/Makefile            |    2 +
 drivers/interconnect/qcom/msm8660.c           | 1008 +++++++++++++++++
 .../dt-bindings/interconnect/qcom,msm8660.h   |  156 +++
 4 files changed, 1176 insertions(+)
 create mode 100644 drivers/interconnect/qcom/msm8660.c
 create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h

-- 
2.43.0


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

* [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs
  2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
  To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
	linux-arm-msm, linux-kernel, linux-pm, Rob Herring

Add the dt-binding interconnect master/slave ID header for the
MSM8x60 family (MSM8260/MSM8660/APQ8060) fabric mesh. The chip's
NoC fabric is split into multiple sub-fabrics that the qnoc-msm8660
driver models:

  AFAB  - Applications fabric (Scorpion CPU + L2)
  SFAB  - System fabric (DMA, SPS, security)
  MMFAB - Multimedia fabric (MDP, GPU, camera, video, rotator)
  DFAB  - Daytona fabric (SDC, ADM master/slave)
  SFPB  - System Fast Peripheral Bridge (RPM, MPM, PMIC SSBI)
  CFPB  - CPU Subsystem Fast Peripheral Bus (GSBI UART/QUP, USB FS,
          TSIF, TSSC, PDM, PRNG)

IDs derived from the legacy webOS msm_bus_board_8660.c master/slave
enums, normalised to the upstream interconnect-framework naming.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 .../dt-bindings/interconnect/qcom,msm8660.h   | 156 ++++++++++++++++++
 1 file changed, 156 insertions(+)
 create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h

diff --git a/include/dt-bindings/interconnect/qcom,msm8660.h b/include/dt-bindings/interconnect/qcom,msm8660.h
new file mode 100644
index 000000000000..d2639f737a54
--- /dev/null
+++ b/include/dt-bindings/interconnect/qcom,msm8660.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) interconnect IDs
+ *
+ * Copyright (c) 2026, Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Based on webOS kernel msm_bus_board_8660.c
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ */
+
+#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H
+#define __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H
+
+/*
+ * MSM8x60 has a fabric-based bus architecture:
+ * - APPSS Fabric: CPU and memory interface
+ * - System Fabric: System peripherals and DMA
+ * - MMSS Fabric: Multimedia subsystem (display, camera, video)
+ * - Daytona Fabric: Peripheral bus (SDCC, ADM DMA)
+ * - System FPB: System Fast Peripheral Bus
+ * - CPSS FPB: CPU Subsystem Fast Peripheral Bus
+ */
+
+/* APPSS Fabric - Apps processor fabric */
+#define AFAB_MAS_AMPSS_M0		0
+#define AFAB_MAS_AMPSS_M1		1
+#define AFAB_SLV_EBI_CH0		2
+#define AFAB_SLV_AMPSS_L2		3
+#define AFAB_TO_MMSS			4
+#define AFAB_TO_SYSTEM			5
+
+/* System Fabric - System bus */
+#define SFAB_MAS_APPSS			0
+#define SFAB_MAS_SPS			1
+#define SFAB_MAS_ADM0_PORT0		2
+#define SFAB_MAS_ADM0_PORT1		3
+#define SFAB_MAS_ADM1_PORT0		4
+#define SFAB_MAS_ADM1_PORT1		5
+#define SFAB_MAS_LPASS_PROC		6
+#define SFAB_MAS_MSS_PROCI		7
+#define SFAB_MAS_MSS_PROCD		8
+#define SFAB_MAS_MSS_MDM_PORT0		9
+#define SFAB_MAS_LPASS			10
+#define SFAB_MAS_MMSS_FPB		11
+#define SFAB_MAS_ADM1_CI		12
+#define SFAB_MAS_ADM0_CI		13
+#define SFAB_MAS_MSS_MDM_PORT1		14
+#define SFAB_MAS_USB_HS			15
+#define SFAB_TO_APPSS			16
+#define SFAB_TO_SYSTEM_FPB		17
+#define SFAB_TO_CPSS_FPB		18
+#define SFAB_SLV_SPS			19
+#define SFAB_SLV_SYSTEM_IMEM		20
+#define SFAB_SLV_AMPSS			21
+#define SFAB_SLV_MSS			22
+#define SFAB_SLV_LPASS			23
+#define SFAB_SLV_MMSS_FPB		24
+#define SFAB_TO_DFAB			25
+
+/* MMSS Fabric - Multimedia subsystem */
+#define MMFAB_MAS_MDP_PORT0		0
+#define MMFAB_MAS_MDP_PORT1		1
+#define MMFAB_MAS_ADM1_PORT0		2
+#define MMFAB_MAS_ROTATOR		3
+#define MMFAB_MAS_GRAPHICS_3D		4
+#define MMFAB_MAS_JPEG_DEC		5
+#define MMFAB_MAS_GRAPHICS_2D_CORE0	6
+#define MMFAB_MAS_VFE			7
+#define MMFAB_MAS_VPE			8
+#define MMFAB_MAS_JPEG_ENC		9
+#define MMFAB_MAS_GRAPHICS_2D_CORE1	10
+#define MMFAB_MAS_HD_CODEC_PORT0	11
+#define MMFAB_MAS_HD_CODEC_PORT1	12
+#define MMFAB_TO_APPSS			13
+#define MMFAB_SLV_SMI			14
+#define MMFAB_SLV_MM_IMEM		15
+
+/*
+ * Daytona Fabric (DFAB) - Peripheral bus
+ *
+ * DFAB connects slower peripherals (SDCC, ADM DMA) to the system fabric.
+ * The webOS kernel managed DFAB bandwidth via voter clocks (dfab_sdc*_clk,
+ * dfab_usb_hs_clk). In mainline, this is handled by the interconnect framework.
+ *
+ * USB HS is included as a DFAB voter for compatibility with the legacy clock
+ * voting mechanism. The webOS kernel comment said: "if usb link is in sps
+ * there is no need for usb pclk as daytona fabric clock will be used instead".
+ * This keeps DFAB clock stable when USB is active.
+ */
+#define DFAB_MAS_SDC1			0
+#define DFAB_MAS_SDC2			1
+#define DFAB_MAS_SDC3			2
+#define DFAB_MAS_SDC4			3
+#define DFAB_MAS_SDC5			4
+#define DFAB_MAS_ADM0_MASTER		5
+#define DFAB_MAS_ADM1_MASTER		6
+#define DFAB_TO_SFAB			7
+#define DFAB_SLV_SDC1			8
+#define DFAB_SLV_SDC2			9
+#define DFAB_SLV_SDC3			10
+#define DFAB_SLV_SDC4			11
+#define DFAB_SLV_SDC5			12
+#define DFAB_MAS_USB_HS			13
+#define DFAB_MAS_DSPS			14
+
+/* System FPB - Slow peripheral bus for system */
+#define SFPB_MAS_SYSTEM			0
+#define SFPB_MAS_SPDM			1
+#define SFPB_MAS_RPM			2
+#define SFPB_SLV_SPDM			3
+#define SFPB_SLV_RPM			4
+#define SFPB_SLV_RPM_MSG_RAM		5
+#define SFPB_SLV_MPM			6
+#define SFPB_SLV_PMIC1_SSBI1_A		7
+#define SFPB_SLV_PMIC1_SSBI1_B		8
+#define SFPB_SLV_PMIC1_SSBI1_C		9
+#define SFPB_SLV_PMIC2_SSBI2_A		10
+#define SFPB_SLV_PMIC2_SSBI2_B		11
+
+/* CPSS FPB - CPU subsystem fast peripheral bus */
+#define CFPB_MAS_SYSTEM			0
+#define CFPB_SLV_GSBI1_UART		1
+#define CFPB_SLV_GSBI2_UART		2
+#define CFPB_SLV_GSBI3_UART		3
+#define CFPB_SLV_GSBI4_UART		4
+#define CFPB_SLV_GSBI5_UART		5
+#define CFPB_SLV_GSBI6_UART		6
+#define CFPB_SLV_GSBI7_UART		7
+#define CFPB_SLV_GSBI8_UART		8
+#define CFPB_SLV_GSBI9_UART		9
+#define CFPB_SLV_GSBI10_UART		10
+#define CFPB_SLV_GSBI11_UART		11
+#define CFPB_SLV_GSBI12_UART		12
+#define CFPB_SLV_GSBI1_QUP		13
+#define CFPB_SLV_GSBI2_QUP		14
+#define CFPB_SLV_GSBI3_QUP		15
+#define CFPB_SLV_GSBI4_QUP		16
+#define CFPB_SLV_GSBI5_QUP		17
+#define CFPB_SLV_GSBI6_QUP		18
+#define CFPB_SLV_GSBI7_QUP		19
+#define CFPB_SLV_GSBI8_QUP		20
+#define CFPB_SLV_GSBI9_QUP		21
+#define CFPB_SLV_GSBI10_QUP		22
+#define CFPB_SLV_GSBI11_QUP		23
+#define CFPB_SLV_GSBI12_QUP		24
+#define CFPB_SLV_EBI2_NAND		25
+#define CFPB_SLV_USB_FS1		26
+#define CFPB_SLV_USB_FS2		27
+#define CFPB_SLV_TSIF			28
+#define CFPB_SLV_MSM_TSSC		29
+#define CFPB_SLV_MSM_PDM		30
+#define CFPB_SLV_MSM_DIMEM		31
+#define CFPB_SLV_MSM_TCSR		32
+#define CFPB_SLV_MSM_PRNG		33
+
+#endif /* __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H */
-- 
2.43.0


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

* [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver
  2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 0/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
  To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
	linux-arm-msm, linux-kernel, linux-pm, Rob Herring

Add a Qualcomm interconnect driver for the MSM8x60 family
(MSM8260/MSM8660/APQ8060), modelling the four fabrics that connect
masters and slaves on these Scorpion-class SoCs:

  - AFAB  : Application/CPU fabric
  - SFAB  : System fabric (peripherals, USB, SDCC, etc.)
  - MMFAB : Multimedia fabric (MDP, VFE, VIDC, GPU, JPEG, VPE, ROT)
  - DFAB  : Daytona fabric (low-bandwidth peripherals)

The driver implements the interconnect-provider API so that consumer
drivers (display, camera, video, GPU, USB, MMC) can request bandwidth
between specific masters and slaves via icc_set_bw(), letting the
firmware-managed bus-scaling decide actual NoC clock rates.

Used on the HP TouchPad (Tenderloin) and on the Pre3 / Veer
form-factors; without it, the multimedia and storage paths are
starved of bandwidth and run at minimum NoC clocks.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/interconnect/qcom/Kconfig   |   10 +
 drivers/interconnect/qcom/Makefile  |    2 +
 drivers/interconnect/qcom/msm8660.c | 1008 +++++++++++++++++++++++++++
 3 files changed, 1020 insertions(+)
 create mode 100644 drivers/interconnect/qcom/msm8660.c

diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig
index 786b4eda44b4..17364be522c7 100644
--- a/drivers/interconnect/qcom/Kconfig
+++ b/drivers/interconnect/qcom/Kconfig
@@ -80,6 +80,16 @@ config INTERCONNECT_QCOM_MSM8953
 	  This is a driver for the Qualcomm Network-on-Chip on msm8953-based
 	  platforms.
 
+config INTERCONNECT_QCOM_MSM8660
+	tristate "Qualcomm MSM8x60 interconnect driver"
+	depends on INTERCONNECT_QCOM
+	depends on MFD_QCOM_RPM
+	help
+	  This is a driver for the Qualcomm fabric-based bus interconnect
+	  on MSM8x60 family (MSM8260/MSM8660/APQ8060) platforms (e.g., HP TouchPad).
+	  The driver manages APPSS, System, and MMSS fabrics and sends
+	  per-port bandwidth arbitration requests to RPM firmware.
+
 config INTERCONNECT_QCOM_MSM8974
 	tristate "Qualcomm MSM8974 interconnect driver"
 	depends on INTERCONNECT_QCOM
diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile
index cdf2c6c9fbf3..0c849c30a907 100644
--- a/drivers/interconnect/qcom/Makefile
+++ b/drivers/interconnect/qcom/Makefile
@@ -13,6 +13,7 @@ qnoc-msm8916-objs			:= msm8916.o
 qnoc-msm8937-objs			:= msm8937.o
 qnoc-msm8939-objs			:= msm8939.o
 qnoc-msm8953-objs			:= msm8953.o
+qnoc-msm8660-objs			:= msm8660.o
 qnoc-msm8974-objs			:= msm8974.o
 qnoc-msm8976-objs			:= msm8976.o
 qnoc-msm8996-objs			:= msm8996.o
@@ -58,6 +59,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8937) += qnoc-msm8937.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8939) += qnoc-msm8939.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8953) += qnoc-msm8953.o
+obj-$(CONFIG_INTERCONNECT_QCOM_MSM8660) += qnoc-msm8660.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8976) += qnoc-msm8976.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8996) += qnoc-msm8996.o
diff --git a/drivers/interconnect/qcom/msm8660.c b/drivers/interconnect/qcom/msm8660.c
new file mode 100644
index 000000000000..eab5b928f681
--- /dev/null
+++ b/drivers/interconnect/qcom/msm8660.c
@@ -0,0 +1,1008 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) interconnect driver
+ *
+-2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Based on msm8974.c by Brian Masney <masneyb@onstation.org>
+ * and webOS kernel msm_bus_board_8660.c / msm_bus_fabric.c
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * MSM8x60 has a fabric-based bus architecture:
+ *
+ *              +------------------+
+ *              |   APPSS Fabric   |  (CPU, L2, Memory)
+ *              +--------+---------+
+ *                       |
+ *         +-------------+-------------+
+ *         |                           |
+ *  +------+------+            +-------+-------+
+ *  | MMSS Fabric |            | System Fabric |
+ *  | (Display,   |            | (Peripherals, |
+ *  |  Camera,    |            |  DMA, etc)    |
+ *  |  Video)     |            +-------+-------+
+ *  +-------------+                    |
+ *                           +---------+---------+
+ *                           |                   |
+ *                    +------+------+     +------+------+
+ *                    | System FPB  |     |  CPSS FPB   |
+ *                    | (RPM, PMIC) |     | (GSBI, USB) |
+ *                    +-------------+     +-------------+
+ *
+ * Each fabric has an RPM arbitration interface that programs per-port
+ * bandwidth and priority tier via MM_FABRIC_ARB / SYS_FABRIC_ARB /
+ * APPS_FABRIC_ARB registers.  The webOS kernel sent these as packed
+ * u16 arrays (bwsum + arb) through msm_rpm_set().  This driver uses
+ * the mainline qcom_rpm_write() interface to do the same.
+ */
+
+#include <dt-bindings/interconnect/qcom,msm8660.h>
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+#include <linux/args.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interconnect-provider.h>
+#include <linux/io.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* Internal node IDs - these map to the DT binding IDs plus fabric offset */
+enum {
+	/* APPSS Fabric nodes */
+	MSM8660_AFAB_MAS_AMPSS_M0 = 1,
+	MSM8660_AFAB_MAS_AMPSS_M1,
+	MSM8660_AFAB_SLV_EBI_CH0,
+	MSM8660_AFAB_SLV_AMPSS_L2,
+	MSM8660_AFAB_TO_MMSS,
+	MSM8660_AFAB_TO_SYSTEM,
+
+	/* System Fabric nodes */
+	MSM8660_SFAB_MAS_APPSS,
+	MSM8660_SFAB_MAS_SPS,
+	MSM8660_SFAB_MAS_ADM0_PORT0,
+	MSM8660_SFAB_MAS_ADM0_PORT1,
+	MSM8660_SFAB_MAS_ADM1_PORT0,
+	MSM8660_SFAB_MAS_ADM1_PORT1,
+	MSM8660_SFAB_MAS_LPASS_PROC,
+	MSM8660_SFAB_MAS_MSS_PROCI,
+	MSM8660_SFAB_MAS_MSS_PROCD,
+	MSM8660_SFAB_MAS_MSS_MDM_PORT0,
+	MSM8660_SFAB_MAS_LPASS,
+	MSM8660_SFAB_MAS_MMSS_FPB,
+	MSM8660_SFAB_MAS_ADM1_CI,
+	MSM8660_SFAB_MAS_ADM0_CI,
+	MSM8660_SFAB_MAS_MSS_MDM_PORT1,
+	MSM8660_SFAB_MAS_USB_HS,
+	MSM8660_SFAB_TO_APPSS,
+	MSM8660_SFAB_TO_SYSTEM_FPB,
+	MSM8660_SFAB_TO_CPSS_FPB,
+	MSM8660_SFAB_SLV_SPS,
+	MSM8660_SFAB_SLV_SYSTEM_IMEM,
+	MSM8660_SFAB_SLV_AMPSS,
+	MSM8660_SFAB_SLV_MSS,
+	MSM8660_SFAB_SLV_LPASS,
+	MSM8660_SFAB_SLV_MMSS_FPB,
+	MSM8660_SFAB_TO_DFAB,
+
+	/* Daytona Fabric nodes (DFAB) - peripheral bus */
+	MSM8660_DFAB_MAS_SDC1,
+	MSM8660_DFAB_MAS_SDC2,
+	MSM8660_DFAB_MAS_SDC3,
+	MSM8660_DFAB_MAS_SDC4,
+	MSM8660_DFAB_MAS_SDC5,
+	MSM8660_DFAB_MAS_ADM0_MASTER,
+	MSM8660_DFAB_MAS_ADM1_MASTER,
+	MSM8660_DFAB_TO_SFAB,
+	MSM8660_DFAB_SLV_SDC1,
+	MSM8660_DFAB_SLV_SDC2,
+	MSM8660_DFAB_SLV_SDC3,
+	MSM8660_DFAB_SLV_SDC4,
+	MSM8660_DFAB_SLV_SDC5,
+	MSM8660_DFAB_MAS_USB_HS,	/* USB HS DFAB voter */
+	MSM8660_DFAB_MAS_DSPS,		/* DSPS DFAB voter */
+
+	/* MMSS Fabric nodes */
+	MSM8660_MMFAB_MAS_MDP_PORT0,
+	MSM8660_MMFAB_MAS_MDP_PORT1,
+	MSM8660_MMFAB_MAS_ADM1_PORT0,
+	MSM8660_MMFAB_MAS_ROTATOR,
+	MSM8660_MMFAB_MAS_GRAPHICS_3D,
+	MSM8660_MMFAB_MAS_JPEG_DEC,
+	MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE0,
+	MSM8660_MMFAB_MAS_VFE,
+	MSM8660_MMFAB_MAS_VPE,
+	MSM8660_MMFAB_MAS_JPEG_ENC,
+	MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE1,
+	MSM8660_MMFAB_MAS_HD_CODEC_PORT0,
+	MSM8660_MMFAB_MAS_HD_CODEC_PORT1,
+	MSM8660_MMFAB_TO_APPSS,
+	MSM8660_MMFAB_SLV_SMI,
+	MSM8660_MMFAB_SLV_MM_IMEM,
+};
+
+#define to_msm8660_icc_provider(_provider) \
+	container_of(_provider, struct msm8660_icc_provider, provider)
+
+/*
+ * Minimum fabric clock rate to prevent bus starvation.
+ *
+ * When no consumers request bandwidth, the rate calculation yields 0,
+ * causing fabric clocks to drop to minimum. This creates bimodal
+ * performance: fast when other subsystems (like display) happen to
+ * request bandwidth, slow otherwise.
+ *
+ * 384 MHz keeps fabric fast during concurrent MDP display scanout
+ * and USB gadget traffic. webOS docs: "AXI bus frequency needs to be
+ * kept at maximum value while USB data transfers are happening."
+ * 266 MHz was insufficient - USB crashed during display activity.
+ */
+#define MSM8660_FABRIC_MIN_RATE		384000000UL	/* 384 MHz */
+
+/*
+ * Maximum RPM ARB buffer size across all fabrics.
+ * MM fabric is largest at 23 u32 words.
+ */
+#define MSM8660_MAX_RPM_BUF		23
+
+/*
+ * RPM fabric arbitration data format (from webOS msm_bus_fabric.c):
+ *
+ * Each u16 arb entry: bit 15 = tier (1=TIER1 high priority), bits 14-0 = BW
+ * Bandwidth is in 128KB units (bytes >> 17).
+ * Two u16 values are packed into each u32 RPM register word.
+ *
+ * Buffer layout: [bwsum pairs] [arb pairs]
+ * bwsum[slave_port] = total bandwidth to that slave
+ * arb[(tier-1)*nmasters + master_port] = per-master arbitration entry
+ */
+#define ARB_BWMASK		0x7FFF
+#define ARB_TIERMASK		0x8000
+#define ARB_TIER1		1
+#define ARB_TIER2		2
+
+static const struct clk_bulk_data msm8660_afab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+	{ .id = "ebi1" },
+	{ .id = "ebi1_a" },
+};
+
+static const struct clk_bulk_data msm8660_sfab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+};
+
+static const struct clk_bulk_data msm8660_mmfab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+	{ .id = "smi" },
+	{ .id = "smi_a" },
+};
+
+static const struct clk_bulk_data msm8660_dfab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+};
+
+/**
+ * struct msm8660_icc_node - MSM8660 specific interconnect nodes
+ * @name: the node name used in debugfs
+ * @id: a unique node identifier
+ * @links: an array of nodes where we can go next while traversing
+ * @num_links: the total number of @links
+ * @buswidth: width of the interconnect between a node and the bus (bytes)
+ * @rate: current bus clock rate in Hz
+ * @mas_port: master port index for RPM ARB (-1 if not a master)
+ * @slv_port: slave port index for RPM bwsum (-1 if not a slave)
+ * @mas_tier: master priority tier (ARB_TIER1 or ARB_TIER2, 0 if N/A)
+ */
+struct msm8660_icc_node {
+	unsigned char *name;
+	u16 id;
+#define MSM8660_ICC_MAX_LINKS	3
+	u16 links[MSM8660_ICC_MAX_LINKS];
+	u16 num_links;
+	u16 buswidth;
+	u64 rate;
+	s8 mas_port;
+	s8 slv_port;
+	u8 mas_tier;
+};
+
+/**
+ * struct msm8660_icc_desc - Fabric descriptor
+ * @nodes: array of node pointers
+ * @num_nodes: number of nodes
+ * @bus_clks: clock definitions
+ * @num_clks: number of clocks
+ * @rpm_resource: QCOM_RPM_*_FABRIC_ARB constant, or -1 for no ARB
+ * @nmasters: number of master ports in this fabric (for ARB array sizing)
+ * @nslaves: number of slave ports in this fabric (for bwsum array sizing)
+ * @ntieredslaves: number of tiered slaves (ARB rows)
+ * @default_tiered_slave: 1-based index of default tiered slave for masters
+ * @rpm_buf_size: number of u32 words for RPM write
+ */
+struct msm8660_icc_desc {
+	struct msm8660_icc_node * const *nodes;
+	size_t num_nodes;
+	const struct clk_bulk_data *bus_clks;
+	size_t num_clks;
+	int rpm_resource;
+	u8 nmasters;
+	u8 nslaves;
+	u8 ntieredslaves;
+	u8 default_tiered_slave;
+	u8 rpm_buf_size;
+};
+
+/**
+ * struct msm8660_icc_provider - MSM8660 specific interconnect provider
+ * @provider: generic interconnect provider
+ * @bus_clks: the clk_bulk_data table of bus clocks
+ * @num_clks: the total number of clk_bulk_data entries
+ * @rpm: RPM handle for fabric arbitration writes
+ * @desc: fabric descriptor with RPM metadata
+ * @arb: pre-allocated arbitration array (nmasters * ntieredslaves u16 entries)
+ * @bwsum: pre-allocated bandwidth sum array (nslaves u16 entries)
+ * @rpm_buf: pre-allocated RPM write buffer (rpm_buf_size u32 entries)
+ */
+struct msm8660_icc_provider {
+	struct icc_provider provider;
+	struct clk_bulk_data *bus_clks;
+	int num_clks;
+	struct qcom_rpm *rpm;
+	const struct msm8660_icc_desc *desc;
+	u16 *arb;
+	u16 *bwsum;
+	u32 *rpm_buf;
+};
+
+/*
+ * Node definitions with RPM port mapping.
+ *
+ * DEFINE_QNODE(_name, _id, _buswidth, _mas_port, _slv_port, _tier, links...)
+ *
+ * _mas_port: master port index for RPM ARB array (-1 if not a master)
+ * _slv_port: slave port index for RPM bwsum array (-1 if not a slave)
+ * _tier: master priority tier (ARB_TIER1=1, ARB_TIER2=2, 0 if N/A)
+ */
+#define DEFINE_QNODE(_name, _id, _buswidth, _mas, _slv, _tier, ...)	\
+	static struct msm8660_icc_node _name = {			\
+		.name = #_name,						\
+		.id = _id,						\
+		.buswidth = _buswidth,					\
+		.num_links = COUNT_ARGS(__VA_ARGS__),			\
+		.links = { __VA_ARGS__ },				\
+		.mas_port = _mas,					\
+		.slv_port = _slv,					\
+		.mas_tier = _tier,					\
+	}
+
+/*
+ * =========================================================================
+ * APPSS Fabric nodes
+ *
+ * 4 masters, 4 slaves, 2 tiered slaves
+ * Master ports: SMPSS_M0=0, SMPSS_M1=1, FAB_MMSS=2, FAB_SYSTEM=3
+ * Slave ports:  EBI_CH0=0, SMPSS_L2=1, MMSS_FAB=2, SYSTEM_FAB=3
+ * Default target: tiered slave 1 (EBI_CH0)
+ * =========================================================================
+ */
+DEFINE_QNODE(mas_ampss_m0, MSM8660_AFAB_MAS_AMPSS_M0, 8, 0, -1, ARB_TIER2,
+	     MSM8660_AFAB_SLV_EBI_CH0, MSM8660_AFAB_TO_MMSS, MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(mas_ampss_m1, MSM8660_AFAB_MAS_AMPSS_M1, 8, 1, -1, ARB_TIER2,
+	     MSM8660_AFAB_SLV_EBI_CH0, MSM8660_AFAB_TO_MMSS, MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(slv_ebi_ch0, MSM8660_AFAB_SLV_EBI_CH0, 8, -1, 0, 0);
+DEFINE_QNODE(slv_ampss_l2, MSM8660_AFAB_SLV_AMPSS_L2, 8, -1, 1, 0);
+/*
+ * Gateway nodes need links to both the cross-fabric gateway AND the memory
+ * slave to enable cross-fabric paths. Without link to EBI_CH0, path_find()
+ * can't route from MMSS/System fabric masters to main memory.
+ *
+ * AFAB_TO_MMSS doubles as AFAB master port 2 (the FAB_MMSS master). MDP
+ * scanout and GPU traffic enter AFAB through this gateway. Mark it
+ * ARB_TIER1 so display/multimedia traffic keeps priority over CPU L2
+ * misses inside the APPSS fabric — without this, MDP TIER1 priority
+ * earned in MMFAB is dropped at the AFAB boundary and MDP fetches lose
+ * arbitration to CPU traffic, producing PRIMARY_INTF_UDERRUN.
+ */
+DEFINE_QNODE(afab_to_mmss, MSM8660_AFAB_TO_MMSS, 8, 2, 2, ARB_TIER1,
+	     MSM8660_MMFAB_TO_APPSS, MSM8660_AFAB_SLV_EBI_CH0);
+DEFINE_QNODE(afab_to_system, MSM8660_AFAB_TO_SYSTEM, 8, 3, 3, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS, MSM8660_AFAB_SLV_EBI_CH0);
+
+static struct msm8660_icc_node * const msm8660_afab_nodes[] = {
+	[AFAB_MAS_AMPSS_M0] = &mas_ampss_m0,
+	[AFAB_MAS_AMPSS_M1] = &mas_ampss_m1,
+	[AFAB_SLV_EBI_CH0] = &slv_ebi_ch0,
+	[AFAB_SLV_AMPSS_L2] = &slv_ampss_l2,
+	[AFAB_TO_MMSS] = &afab_to_mmss,
+	[AFAB_TO_SYSTEM] = &afab_to_system,
+};
+
+static const struct msm8660_icc_desc msm8660_afab = {
+	.nodes = msm8660_afab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_afab_nodes),
+	.bus_clks = msm8660_afab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_afab_clocks),
+	.rpm_resource = QCOM_RPM_APPS_FABRIC_ARB,
+	.nmasters = 4,
+	.nslaves = 4,
+	.ntieredslaves = 2,
+	.default_tiered_slave = 1,	/* EBI_CH0 */
+	.rpm_buf_size = 6,
+};
+
+/*
+ * =========================================================================
+ * System Fabric nodes
+ *
+ * 17 masters, 9 slaves, 2 tiered slaves
+ * Master ports: see enum msm_bus_8660_master_ports_type in webOS
+ * Slave ports:  APPSS_FAB=0, SPS=1, SYSTEM_IMEM=2, SMPSS=3, MSS=4,
+ *               LPASS=5, CPSS_FPB=6, SYSTEM_FPB=7, MMSS_FPB=8
+ * Default target: tiered slave 1 (APPSS gateway)
+ * =========================================================================
+ */
+DEFINE_QNODE(sfab_mas_appss, MSM8660_SFAB_MAS_APPSS, 8, 0, -1, ARB_TIER2,
+	     MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(sfab_mas_sps, MSM8660_SFAB_MAS_SPS, 8, 1, -1, ARB_TIER2,
+	     MSM8660_SFAB_SLV_SPS);
+/*
+ * ADM DMA masters - route through SFAB_TO_APPSS to reach EBI memory.
+ * Path: ADM -> SFAB_TO_APPSS -> AFAB_TO_SYSTEM -> AFAB_SLV_EBI_CH0
+ * This enables proper EBI bandwidth voting for DMA operations.
+ */
+DEFINE_QNODE(sfab_mas_adm0_port0, MSM8660_SFAB_MAS_ADM0_PORT0, 8, 2, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm0_port1, MSM8660_SFAB_MAS_ADM0_PORT1, 8, 3, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm1_port0, MSM8660_SFAB_MAS_ADM1_PORT0, 8, 4, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm1_port1, MSM8660_SFAB_MAS_ADM1_PORT1, 8, 5, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_lpass_proc, MSM8660_SFAB_MAS_LPASS_PROC, 8, 6, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_proci, MSM8660_SFAB_MAS_MSS_PROCI, 8, 7, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_procd, MSM8660_SFAB_MAS_MSS_PROCD, 8, 8, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_mdm_port0, MSM8660_SFAB_MAS_MSS_MDM_PORT0, 8, 9, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_lpass, MSM8660_SFAB_MAS_LPASS, 8, 10, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mmss_fpb, MSM8660_SFAB_MAS_MMSS_FPB, 8, 13, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_adm1_ci, MSM8660_SFAB_MAS_ADM1_CI, 8, 14, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_adm0_ci, MSM8660_SFAB_MAS_ADM0_CI, 8, 15, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_mdm_port1, MSM8660_SFAB_MAS_MSS_MDM_PORT1, 8, 16, -1, ARB_TIER2);
+/* USB HS has no dedicated master port in webOS SFAB - bandwidth voting only */
+DEFINE_QNODE(sfab_mas_usb_hs, MSM8660_SFAB_MAS_USB_HS, 8, -1, -1, 0,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_to_appss, MSM8660_SFAB_TO_APPSS, 8, -1, 0, 0,
+	     MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(sfab_to_system_fpb, MSM8660_SFAB_TO_SYSTEM_FPB, 4, -1, 7, 0);
+DEFINE_QNODE(sfab_to_cpss_fpb, MSM8660_SFAB_TO_CPSS_FPB, 4, -1, 6, 0);
+DEFINE_QNODE(sfab_slv_sps, MSM8660_SFAB_SLV_SPS, 8, -1, 1, 0);
+DEFINE_QNODE(sfab_slv_system_imem, MSM8660_SFAB_SLV_SYSTEM_IMEM, 8, -1, 2, 0);
+DEFINE_QNODE(sfab_slv_ampss, MSM8660_SFAB_SLV_AMPSS, 8, -1, 3, 0);
+DEFINE_QNODE(sfab_slv_mss, MSM8660_SFAB_SLV_MSS, 8, -1, 4, 0);
+DEFINE_QNODE(sfab_slv_lpass, MSM8660_SFAB_SLV_LPASS, 8, -1, 5, 0);
+DEFINE_QNODE(sfab_slv_mmss_fpb, MSM8660_SFAB_SLV_MMSS_FPB, 8, -1, 8, 0);
+/*
+ * Gateway to DFAB: links to DFAB_TO_SFAB for path traversal.
+ * Also links to SFAB_TO_APPSS to enable DFAB->SFAB->AFAB->memory paths.
+ * No slave port in webOS SFAB config (DFAB is separate fabric).
+ */
+DEFINE_QNODE(sfab_to_dfab, MSM8660_SFAB_TO_DFAB, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB, MSM8660_SFAB_TO_APPSS);
+
+static struct msm8660_icc_node * const msm8660_sfab_nodes[] = {
+	[SFAB_MAS_APPSS] = &sfab_mas_appss,
+	[SFAB_MAS_SPS] = &sfab_mas_sps,
+	[SFAB_MAS_ADM0_PORT0] = &sfab_mas_adm0_port0,
+	[SFAB_MAS_ADM0_PORT1] = &sfab_mas_adm0_port1,
+	[SFAB_MAS_ADM1_PORT0] = &sfab_mas_adm1_port0,
+	[SFAB_MAS_ADM1_PORT1] = &sfab_mas_adm1_port1,
+	[SFAB_MAS_LPASS_PROC] = &sfab_mas_lpass_proc,
+	[SFAB_MAS_MSS_PROCI] = &sfab_mas_mss_proci,
+	[SFAB_MAS_MSS_PROCD] = &sfab_mas_mss_procd,
+	[SFAB_MAS_MSS_MDM_PORT0] = &sfab_mas_mss_mdm_port0,
+	[SFAB_MAS_LPASS] = &sfab_mas_lpass,
+	[SFAB_MAS_MMSS_FPB] = &sfab_mas_mmss_fpb,
+	[SFAB_MAS_ADM1_CI] = &sfab_mas_adm1_ci,
+	[SFAB_MAS_ADM0_CI] = &sfab_mas_adm0_ci,
+	[SFAB_MAS_MSS_MDM_PORT1] = &sfab_mas_mss_mdm_port1,
+	[SFAB_MAS_USB_HS] = &sfab_mas_usb_hs,
+	[SFAB_TO_APPSS] = &sfab_to_appss,
+	[SFAB_TO_SYSTEM_FPB] = &sfab_to_system_fpb,
+	[SFAB_TO_CPSS_FPB] = &sfab_to_cpss_fpb,
+	[SFAB_SLV_SPS] = &sfab_slv_sps,
+	[SFAB_SLV_SYSTEM_IMEM] = &sfab_slv_system_imem,
+	[SFAB_SLV_AMPSS] = &sfab_slv_ampss,
+	[SFAB_SLV_MSS] = &sfab_slv_mss,
+	[SFAB_SLV_LPASS] = &sfab_slv_lpass,
+	[SFAB_SLV_MMSS_FPB] = &sfab_slv_mmss_fpb,
+	[SFAB_TO_DFAB] = &sfab_to_dfab,
+};
+
+static const struct msm8660_icc_desc msm8660_sfab = {
+	.nodes = msm8660_sfab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_sfab_nodes),
+	.bus_clks = msm8660_sfab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_sfab_clocks),
+	.rpm_resource = QCOM_RPM_SYS_FABRIC_ARB,
+	.nmasters = 17,
+	.nslaves = 9,
+	.ntieredslaves = 2,
+	.default_tiered_slave = 1,	/* APPSS gateway */
+	.rpm_buf_size = 22,
+};
+
+/*
+ * =========================================================================
+ * MMSS Fabric nodes - Multimedia subsystem (MDP, camera, video, GPU)
+ *
+ * 14 masters, 4 slaves, 3 tiered slaves
+ * Master ports: MDP0=0, MDP1=1, ADM1=2, ROT=3, 3D=4, JPEG_DEC=5,
+ *               2D_CORE0=6, VFE=7, VPE=8, JPEG_ENC=9, 2D_CORE1=10,
+ *               (APPS_FAB=11), HD_CODEC0=12, HD_CODEC1=13
+ * Slave ports:  SMI=0, APPSS_FAB=1, (APPSS_FAB_1=2), MM_IMEM=3
+ * Tiered slaves: SMI=1, APPSS_FAB=2, MM_IMEM=3
+ * Default target: tiered slave 2 (APPSS gateway -> main memory)
+ *
+ * MDP ports get TIER1 (high priority) for guaranteed display refresh.
+ * All other masters get TIER2 (default priority).
+ * =========================================================================
+ */
+DEFINE_QNODE(mmfab_mas_mdp_port0, MSM8660_MMFAB_MAS_MDP_PORT0, 16, 0, -1, ARB_TIER1,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_mdp_port1, MSM8660_MMFAB_MAS_MDP_PORT1, 16, 1, -1, ARB_TIER1,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_adm1_port0, MSM8660_MMFAB_MAS_ADM1_PORT0, 8, 2, -1, ARB_TIER2);
+DEFINE_QNODE(mmfab_mas_rotator, MSM8660_MMFAB_MAS_ROTATOR, 16, 3, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_3d, MSM8660_MMFAB_MAS_GRAPHICS_3D, 16, 4, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_jpeg_dec, MSM8660_MMFAB_MAS_JPEG_DEC, 16, 5, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_2d_core0, MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE0, 16,
+	     6, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_vfe, MSM8660_MMFAB_MAS_VFE, 16, 7, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_vpe, MSM8660_MMFAB_MAS_VPE, 16, 8, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_jpeg_enc, MSM8660_MMFAB_MAS_JPEG_ENC, 16, 9, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_2d_core1, MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE1, 16,
+	     10, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_hd_codec_port0, MSM8660_MMFAB_MAS_HD_CODEC_PORT0, 16,
+	     12, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_hd_codec_port1, MSM8660_MMFAB_MAS_HD_CODEC_PORT1, 16,
+	     13, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+/*
+ * Gateway from APPSS into MMSS: slave (port 1) for outbound traffic
+ * leaving MMSS, AND master (port 2) for inbound traffic arriving from
+ * APPSS (e.g. CPU memremap_wc accesses to SMI BOs). Without the master
+ * port and the forward links into MMFAB slaves (SMI / MM_IMEM), the
+ * ICC path-finder has no route from AMPSS_M0 to MMFAB_SLV_SMI; the
+ * cross-fabric gateway only worked for outbound traffic (MMSS masters
+ * reaching APPSS slaves like EBI).
+ *
+ * ARB_TIER1 keeps AMPSS->SMI traffic high-priority within MMFAB so
+ * CPU mmap reads/writes to SMI BOs don't get starved by MDP scanout.
+ */
+DEFINE_QNODE(mmfab_to_appss, MSM8660_MMFAB_TO_APPSS, 8, 2, 1, ARB_TIER1,
+	     MSM8660_AFAB_TO_MMSS,
+	     MSM8660_MMFAB_SLV_SMI,
+	     MSM8660_MMFAB_SLV_MM_IMEM);
+DEFINE_QNODE(mmfab_slv_smi, MSM8660_MMFAB_SLV_SMI, 16, -1, 0, 0);
+DEFINE_QNODE(mmfab_slv_mm_imem, MSM8660_MMFAB_SLV_MM_IMEM, 8, -1, 3, 0);
+
+static struct msm8660_icc_node * const msm8660_mmfab_nodes[] = {
+	[MMFAB_MAS_MDP_PORT0] = &mmfab_mas_mdp_port0,
+	[MMFAB_MAS_MDP_PORT1] = &mmfab_mas_mdp_port1,
+	[MMFAB_MAS_ADM1_PORT0] = &mmfab_mas_adm1_port0,
+	[MMFAB_MAS_ROTATOR] = &mmfab_mas_rotator,
+	[MMFAB_MAS_GRAPHICS_3D] = &mmfab_mas_graphics_3d,
+	[MMFAB_MAS_JPEG_DEC] = &mmfab_mas_jpeg_dec,
+	[MMFAB_MAS_GRAPHICS_2D_CORE0] = &mmfab_mas_graphics_2d_core0,
+	[MMFAB_MAS_VFE] = &mmfab_mas_vfe,
+	[MMFAB_MAS_VPE] = &mmfab_mas_vpe,
+	[MMFAB_MAS_JPEG_ENC] = &mmfab_mas_jpeg_enc,
+	[MMFAB_MAS_GRAPHICS_2D_CORE1] = &mmfab_mas_graphics_2d_core1,
+	[MMFAB_MAS_HD_CODEC_PORT0] = &mmfab_mas_hd_codec_port0,
+	[MMFAB_MAS_HD_CODEC_PORT1] = &mmfab_mas_hd_codec_port1,
+	[MMFAB_TO_APPSS] = &mmfab_to_appss,
+	[MMFAB_SLV_SMI] = &mmfab_slv_smi,
+	[MMFAB_SLV_MM_IMEM] = &mmfab_slv_mm_imem,
+};
+
+static const struct msm8660_icc_desc msm8660_mmfab = {
+	.nodes = msm8660_mmfab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_mmfab_nodes),
+	.bus_clks = msm8660_mmfab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_mmfab_clocks),
+	.rpm_resource = QCOM_RPM_MM_FABRIC_ARB,
+	.nmasters = 14,
+	.nslaves = 4,
+	.ntieredslaves = 3,
+	.default_tiered_slave = 2,	/* APPSS gateway */
+	.rpm_buf_size = 23,
+};
+
+/*
+ * =========================================================================
+ * Daytona Fabric (DFAB) nodes - peripheral bus for SDCC and ADM DMA
+ *
+ * DFAB connects slower peripherals to SFAB via the DFAB_TO_SFAB gateway.
+ * SDCC controllers (eMMC, SD card) connect here.
+ *
+ * No RPM ARB for DFAB - it's a simple peripheral bus with clock-only control.
+ *
+ * USB HS is included as a DFAB voter for compatibility with the legacy
+ * webOS kernel clock voting mechanism.
+ * =========================================================================
+ */
+DEFINE_QNODE(dfab_mas_sdc1, MSM8660_DFAB_MAS_SDC1, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc2, MSM8660_DFAB_MAS_SDC2, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc3, MSM8660_DFAB_MAS_SDC3, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc4, MSM8660_DFAB_MAS_SDC4, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc5, MSM8660_DFAB_MAS_SDC5, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_adm0_master, MSM8660_DFAB_MAS_ADM0_MASTER, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_adm1_master, MSM8660_DFAB_MAS_ADM1_MASTER, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_to_sfab, MSM8660_DFAB_TO_SFAB, 8, -1, -1, 0,
+	     MSM8660_SFAB_TO_DFAB);
+DEFINE_QNODE(dfab_slv_sdc1, MSM8660_DFAB_SLV_SDC1, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc2, MSM8660_DFAB_SLV_SDC2, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc3, MSM8660_DFAB_SLV_SDC3, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc4, MSM8660_DFAB_SLV_SDC4, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc5, MSM8660_DFAB_SLV_SDC5, 8, -1, -1, 0);
+/* USB HS DFAB voter - keeps DFAB clock stable during USB activity */
+DEFINE_QNODE(dfab_mas_usb_hs, MSM8660_DFAB_MAS_USB_HS, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+/* DSPS DFAB voter - keeps DFAB clock stable during sensor activity */
+DEFINE_QNODE(dfab_mas_dsps, MSM8660_DFAB_MAS_DSPS, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+
+static struct msm8660_icc_node * const msm8660_dfab_nodes[] = {
+	[DFAB_MAS_SDC1] = &dfab_mas_sdc1,
+	[DFAB_MAS_SDC2] = &dfab_mas_sdc2,
+	[DFAB_MAS_SDC3] = &dfab_mas_sdc3,
+	[DFAB_MAS_SDC4] = &dfab_mas_sdc4,
+	[DFAB_MAS_SDC5] = &dfab_mas_sdc5,
+	[DFAB_MAS_ADM0_MASTER] = &dfab_mas_adm0_master,
+	[DFAB_MAS_ADM1_MASTER] = &dfab_mas_adm1_master,
+	[DFAB_TO_SFAB] = &dfab_to_sfab,
+	[DFAB_SLV_SDC1] = &dfab_slv_sdc1,
+	[DFAB_SLV_SDC2] = &dfab_slv_sdc2,
+	[DFAB_SLV_SDC3] = &dfab_slv_sdc3,
+	[DFAB_SLV_SDC4] = &dfab_slv_sdc4,
+	[DFAB_SLV_SDC5] = &dfab_slv_sdc5,
+	[DFAB_MAS_USB_HS] = &dfab_mas_usb_hs,
+	[DFAB_MAS_DSPS] = &dfab_mas_dsps,
+};
+
+static const struct msm8660_icc_desc msm8660_dfab = {
+	.nodes = msm8660_dfab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_dfab_nodes),
+	.bus_clks = msm8660_dfab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_dfab_clocks),
+	.rpm_resource = -1,		/* No RPM ARB for DFAB */
+};
+
+/*
+ * Pack bwsum[] and arb[] arrays into the u32 RPM buffer.
+ *
+ * Two u16 values are packed per u32 word: lower 16 bits first, upper 16 next.
+ * Layout: [bwsum pairs] then [arb pairs], handling odd boundaries.
+ *
+ * This matches the webOS msm_bus_fabric_rpm_commit() packing algorithm.
+ */
+static void msm8660_pack_rpm_data(const u16 *bwsum, int nslaves,
+				  const u16 *arb, int arb_size,
+				  u32 *buf)
+{
+	int i, index = 0;
+
+	/* Pack bwsum pairs */
+	for (i = 0; i + 1 < nslaves; i += 2) {
+		buf[index] = ((u32)bwsum[i + 1] << 16) | bwsum[i];
+		index++;
+	}
+
+	/* Handle boundary between bwsum and arb for odd nslaves */
+	if (nslaves & 1) {
+		buf[index] = ((u32)arb[0] << 16) | bwsum[i];
+		index++;
+		i = 1;	/* Start arb from index 1 (index 0 already packed) */
+	} else {
+		i = 0;
+	}
+
+	/* Pack arb pairs */
+	for (; i + 1 < arb_size; i += 2) {
+		buf[index] = ((u32)arb[i + 1] << 16) | arb[i];
+		index++;
+	}
+
+	/* Handle odd arb entry at end */
+	if (i < arb_size) {
+		buf[index] = arb[i];
+		index++;
+	}
+}
+
+/*
+ * Send fabric arbitration data to RPM.
+ *
+ * Iterates over all ICC nodes in the provider, builds bwsum/arb arrays
+ * from their aggregated bandwidth, and sends the packed data to RPM.
+ */
+static void msm8660_rpm_commit(struct msm8660_icc_provider *qp)
+{
+	const struct msm8660_icc_desc *desc = qp->desc;
+	struct icc_provider *provider = &qp->provider;
+	int nm = desc->nmasters;
+	int ns = desc->nslaves;
+	int nts = desc->ntieredslaves;
+	int arb_size = nm * nts;
+	int def_ts = desc->default_tiered_slave;
+	struct icc_node *n;
+	int ret;
+
+	memset(qp->bwsum, 0, ns * sizeof(u16));
+	memset(qp->arb, 0, arb_size * sizeof(u16));
+	memset(qp->rpm_buf, 0, desc->rpm_buf_size * sizeof(u32));
+
+	list_for_each_entry(n, &provider->nodes, node_list) {
+		struct msm8660_icc_node *qn = n->data;
+		u64 bw_bytes;
+		u16 bw_128k;
+
+		/* Use max of avg and peak bandwidth, convert to 128KB units */
+		bw_bytes = max(icc_units_to_bps(n->avg_bw),
+			       icc_units_to_bps(n->peak_bw));
+		do_div(bw_bytes, 8);	/* bits -> bytes */
+		bw_128k = (u16)min_t(u64, bw_bytes >> 17, ARB_BWMASK);
+
+		/* Set arb entry for masters at their default tiered slave */
+		if (qn->mas_port >= 0 && qn->mas_port < nm && def_ts > 0) {
+			int idx = (def_ts - 1) * nm + qn->mas_port;
+			u8 tier;
+
+			if (idx < arb_size) {
+				tier = bw_128k ? qn->mas_tier : ARB_TIER2;
+				qp->arb[idx] = (tier == ARB_TIER1 ? ARB_TIERMASK : 0)
+					       | (bw_128k & ARB_BWMASK);
+			}
+		}
+
+		/* Set bwsum for slaves */
+		if (qn->slv_port >= 0 && qn->slv_port < ns)
+			qp->bwsum[qn->slv_port] = bw_128k;
+	}
+
+	msm8660_pack_rpm_data(qp->bwsum, ns, qp->arb, arb_size, qp->rpm_buf);
+
+	ret = qcom_rpm_write(qp->rpm, QCOM_RPM_ACTIVE_STATE,
+			     desc->rpm_resource, qp->rpm_buf,
+			     desc->rpm_buf_size);
+	if (ret)
+		dev_err_ratelimited(provider->dev,
+				    "RPM fabric ARB write failed: %d\n", ret);
+}
+
+static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+	struct msm8660_icc_node *src_qn;
+	struct msm8660_icc_provider *qp;
+	u64 sum_bw, max_peak_bw, rate;
+	u32 agg_avg = 0, agg_peak = 0;
+	struct icc_provider *provider;
+	struct icc_node *n;
+	int ret, i;
+
+	src_qn = src->data;
+	provider = src->provider;
+	qp = to_msm8660_icc_provider(provider);
+
+	list_for_each_entry(n, &provider->nodes, node_list)
+		provider->aggregate(n, 0, n->avg_bw, n->peak_bw,
+				    &agg_avg, &agg_peak);
+
+	sum_bw = icc_units_to_bps(agg_avg);
+	max_peak_bw = icc_units_to_bps(agg_peak);
+
+	rate = max(sum_bw, max_peak_bw);
+	do_div(rate, src_qn->buswidth);
+	/* Apply minimum floor to prevent bus starvation */
+	rate = max_t(u64, rate, MSM8660_FABRIC_MIN_RATE);
+	rate = min_t(u32, rate, INT_MAX);
+
+	if (src_qn->rate != rate) {
+		for (i = 0; i < qp->num_clks; i++) {
+			ret = clk_set_rate(qp->bus_clks[i].clk, rate);
+			if (ret) {
+				dev_err(provider->dev,
+					"%s clk_set_rate error: %d\n",
+					qp->bus_clks[i].id, ret);
+				ret = 0;
+			}
+		}
+		src_qn->rate = rate;
+	}
+
+	/* Send RPM fabric arbitration if available */
+	if (qp->rpm && qp->desc->rpm_resource >= 0)
+		msm8660_rpm_commit(qp);
+
+	return 0;
+}
+
+static int msm8660_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
+{
+	*avg = 0;
+	*peak = 0;
+	return 0;
+}
+
+static struct qcom_rpm *msm8660_get_rpm(struct device *dev)
+{
+	struct device_node *rpm_np;
+	struct platform_device *rpm_pdev;
+	struct qcom_rpm *rpm;
+
+	rpm_np = of_parse_phandle(dev->of_node, "qcom,rpm", 0);
+	if (!rpm_np) {
+		dev_dbg(dev, "no qcom,rpm phandle, RPM ARB disabled\n");
+		return NULL;
+	}
+
+	rpm_pdev = of_find_device_by_node(rpm_np);
+	of_node_put(rpm_np);
+	if (!rpm_pdev) {
+		dev_warn(dev, "RPM device not found, ARB disabled\n");
+		return NULL;
+	}
+
+	rpm = dev_get_drvdata(&rpm_pdev->dev);
+	put_device(&rpm_pdev->dev);
+	if (!rpm) {
+		dev_warn(dev, "RPM not ready, ARB disabled\n");
+		return NULL;
+	}
+
+	return rpm;
+}
+
+static int msm8660_icc_probe(struct platform_device *pdev)
+{
+	const struct msm8660_icc_desc *desc;
+	struct msm8660_icc_node * const *qnodes;
+	struct msm8660_icc_provider *qp;
+	struct device *dev = &pdev->dev;
+	struct icc_onecell_data *data;
+	struct icc_provider *provider;
+	struct icc_node *node;
+	size_t num_nodes, i;
+	int ret;
+
+	desc = of_device_get_match_data(dev);
+	if (!desc)
+		return -EINVAL;
+
+	qnodes = desc->nodes;
+	num_nodes = desc->num_nodes;
+
+	qp = devm_kzalloc(dev, sizeof(*qp), GFP_KERNEL);
+	if (!qp)
+		return -ENOMEM;
+
+	data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes),
+			    GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->num_nodes = num_nodes;
+
+	qp->bus_clks = devm_kmemdup(dev, desc->bus_clks,
+				    desc->num_clks * sizeof(*desc->bus_clks),
+				    GFP_KERNEL);
+	if (!qp->bus_clks)
+		return -ENOMEM;
+
+	qp->num_clks = desc->num_clks;
+
+	/*
+	 * MSM8660 fabric clocks are managed by RPM firmware and may not be
+	 * available in mainline Linux yet. Make them optional.
+	 */
+	ret = devm_clk_bulk_get_optional(dev, qp->num_clks, qp->bus_clks);
+	if (ret) {
+		dev_warn(dev, "Failed to get bus clocks: %d (continuing anyway)\n", ret);
+		qp->num_clks = 0;
+	}
+
+	if (qp->num_clks) {
+		ret = clk_bulk_prepare_enable(qp->num_clks, qp->bus_clks);
+		if (ret) {
+			dev_warn(dev, "Failed to enable bus clocks: %d\n", ret);
+			qp->num_clks = 0;
+		}
+	}
+
+	/* Set up RPM fabric arbitration */
+	qp->desc = desc;
+	if (desc->rpm_resource >= 0) {
+		qp->rpm = msm8660_get_rpm(dev);
+		if (qp->rpm) {
+			int arb_size = desc->nmasters * desc->ntieredslaves;
+
+			qp->bwsum = devm_kcalloc(dev, desc->nslaves,
+						 sizeof(u16), GFP_KERNEL);
+			qp->arb = devm_kcalloc(dev, arb_size + 1,
+					       sizeof(u16), GFP_KERNEL);
+			qp->rpm_buf = devm_kcalloc(dev, desc->rpm_buf_size,
+						   sizeof(u32), GFP_KERNEL);
+			if (!qp->bwsum || !qp->arb || !qp->rpm_buf) {
+				dev_warn(dev, "RPM buffer alloc failed, ARB disabled\n");
+				qp->rpm = NULL;
+			} else {
+				int rc;
+
+				dev_info(dev, "RPM fabric ARB enabled (%d masters, %d slaves, %d tiered)\n",
+					 desc->nmasters, desc->nslaves,
+					 desc->ntieredslaves);
+
+				/*
+				 * One-shot sleep-context vote of zero bandwidth.
+				 * Without an explicit SLEEP_STATE write, RPM has no
+				 * fabric bandwidth target for deep-sleep and may
+				 * keep the active vote applied indefinitely,
+				 * preventing DDR from dropping its rate when CPUs
+				 * power-collapse. The buffer is devm_kcalloc'd so
+				 * it is all-zero at this point — written before
+				 * any consumer can drive an active vote that would
+				 * dirty it.
+				 *
+				 * msm8660_rpm_commit() writes ACTIVE_STATE only;
+				 * SLEEP_STATE remains zero for the provider's
+				 * lifetime, so this vote does not need refreshing.
+				 */
+				rc = qcom_rpm_write(qp->rpm,
+						    QCOM_RPM_SLEEP_STATE,
+						    desc->rpm_resource,
+						    qp->rpm_buf,
+						    desc->rpm_buf_size);
+				if (rc)
+					dev_warn(dev, "RPM fabric sleep vote failed: %d\n",
+						 rc);
+			}
+		}
+	}
+
+	provider = &qp->provider;
+	provider->dev = dev;
+	provider->set = msm8660_icc_set;
+	provider->aggregate = icc_std_aggregate;
+	provider->xlate = of_icc_xlate_onecell;
+	provider->data = data;
+	provider->get_bw = msm8660_get_bw;
+
+	icc_provider_init(provider);
+
+	for (i = 0; i < num_nodes; i++) {
+		size_t j;
+
+		if (!qnodes[i])
+			continue;
+
+		node = icc_node_create(qnodes[i]->id);
+		if (IS_ERR(node)) {
+			ret = PTR_ERR(node);
+			goto err_remove_nodes;
+		}
+
+		node->name = qnodes[i]->name;
+		node->data = qnodes[i];
+		icc_node_add(node, provider);
+
+		dev_dbg(dev, "registered node %s\n", node->name);
+
+		/* populate links */
+		for (j = 0; j < qnodes[i]->num_links; j++)
+			icc_link_create(node, qnodes[i]->links[j]);
+
+		data->nodes[i] = node;
+	}
+
+	ret = icc_provider_register(provider);
+	if (ret)
+		goto err_remove_nodes;
+
+	platform_set_drvdata(pdev, qp);
+
+	dev_info(dev, "MSM8660 interconnect provider registered\n");
+
+	return 0;
+
+err_remove_nodes:
+	icc_nodes_remove(provider);
+	clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+
+	return ret;
+}
+
+static void msm8660_icc_remove(struct platform_device *pdev)
+{
+	struct msm8660_icc_provider *qp = platform_get_drvdata(pdev);
+
+	icc_provider_deregister(&qp->provider);
+	icc_nodes_remove(&qp->provider);
+	if (qp->num_clks)
+		clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+}
+
+static const struct of_device_id msm8660_noc_of_match[] = {
+	{ .compatible = "qcom,msm8660-apps-fabric", .data = &msm8660_afab },
+	{ .compatible = "qcom,msm8660-system-fabric", .data = &msm8660_sfab },
+	{ .compatible = "qcom,msm8660-mmss-fabric", .data = &msm8660_mmfab },
+	{ .compatible = "qcom,msm8660-daytona-fabric", .data = &msm8660_dfab },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, msm8660_noc_of_match);
+
+static struct platform_driver msm8660_noc_driver = {
+	.probe = msm8660_icc_probe,
+	.remove = msm8660_icc_remove,
+	.driver = {
+		.name = "qnoc-msm8660",
+		.of_match_table = msm8660_noc_of_match,
+		.sync_state = icc_sync_state,
+	},
+};
+/*
+ * Register the NOC provider at core_initcall, matching the mainline pattern
+ * used by newer Qualcomm SoCs (sm8450, glymur, qdu1000, sc8280xp, sm8750).
+ *
+ * Why not module_platform_driver (device_initcall)?  drivers/Makefile lists
+ * drivers/interconnect/ at position 189, *after* every ICC-consumer subdir
+ * (clk/ @40, soc/ @46, gpu/ @68, base/mfd/ @76, spmi/ @89, usb/ @106,
+ * i2c/ @116, mmc/ @133, remoteproc/ @158).  Within a single initcall level
+ * execution order = link order, so a device_initcall registration here runs
+ * *after* every consumer has already tried to probe.  Mainline relies on
+ * deferred-probe retry to recover from that, but in this tree some consumer
+ * (apcs-msm8660 + cpufreq cascade suspected) fails to recover within
+ * deferred_probe_timeout=5 and boot dies at the Tux splash with no rootfs.
+ * Empirically confirmed 2026-05-29 with module_platform_driver (commits
+ * 99275d8a8ae9 + ca35c591854c, reverted).
+ *
+ * icc_provider_register does not require icc_init to have run first --
+ * the framework's locks are statically DEFINE_MUTEX'd -- so registering
+ * the provider at core_initcall (before icc_init at subsys_initcall) is
+ * safe, same as mainline sm8450 etc.
+ */
+static int __init msm8660_noc_driver_init(void)
+{
+	return platform_driver_register(&msm8660_noc_driver);
+}
+core_initcall(msm8660_noc_driver_init);
+
+static void __exit msm8660_noc_driver_exit(void)
+{
+	platform_driver_unregister(&msm8660_noc_driver);
+}
+module_exit(msm8660_noc_driver_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 interconnect driver");
+MODULE_LICENSE("GPL v2");
-- 
2.43.0


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

* [PATCH 0/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver
  2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
  2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, linux-arm-msm, linux-kernel, linux-pm,
	Lukasz Luba, Rafael J. Wysocki, Rob Herring, Thara Gopinath,
	van Hazendonk, Zhang Rui

Hi all,

This series adds a Qualcomm SSBI-attached PMIC thermal driver for the
PM8901 over-temperature alarm block. PM8901 is the companion PMIC paired
with PM8058 on the MSM8x60 family (MSM8260/MSM8660/APQ8060); unlike the
TSENS-based thermal blocks on newer SoCs, PM8901 only exposes a stage-
based alarm (no raw ADC) with four selectable thresholds and three
escalating stages.

The driver registers a thermal-of sensor so a board device tree can
declare trip points and a critical-trip action (e.g. orderly_poweroff).

Used on the HP TouchPad (Tenderloin) as the secondary-PMIC-die thermal
sensor; the primary PMIC PM8058 already has a thermal driver in tree.

Thanks,
Herman

Herman van Hazendonk (2):
  dt-bindings: thermal: qcom: add pm8901-temp-alarm
  thermal: qcom: add PM8901 PMIC temperature-alarm driver

 .../thermal/qcom,pm8901-temp-alarm.yaml       |  79 ++++
 drivers/thermal/qcom/Kconfig                  |  12 +
 drivers/thermal/qcom/Makefile                 |   1 +
 drivers/thermal/qcom/qcom-pm8901-tm.c         | 341 ++++++++++++++++++
 4 files changed, 433 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
 create mode 100644 drivers/thermal/qcom/qcom-pm8901-tm.c

-- 
2.43.0


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

* [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm
  2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
                   ` (2 preceding siblings ...)
  2026-05-30 14:00 ` [PATCH 0/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
  2026-05-30 20:48   ` Rob Herring (Arm)
  2026-05-30 14:00 ` [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
  5 siblings, 1 reply; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, linux-arm-msm, linux-kernel, linux-pm,
	Lukasz Luba, Rafael J. Wysocki, Rob Herring, Thara Gopinath,
	van Hazendonk, Zhang Rui

Add the binding for the temperature-alarm block inside the Qualcomm
PM8901 PMIC (companion to the PM8058 on MSM8x60). The driver exposes
the PM8901 die-temperature trip stages (105 / 125 / 145 C) to the
thermal framework via a thermal-zone, with stage 3 wired as a
critical trip so the kernel issues orderly_poweroff() when the part
overheats.

The binding describes the SSBI sub-node address, the GIC interrupt
the alarm raises on a stage transition, and the parent PMIC
reference.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 .../thermal/qcom,pm8901-temp-alarm.yaml       | 79 +++++++++++++++++++
 1 file changed, 79 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml

diff --git a/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml b/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
new file mode 100644
index 000000000000..569943b4aedc
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/qcom,pm8901-temp-alarm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm PM8901 PMIC Temperature Alarm
+
+maintainers:
+  - Herman van Hazendonk <github.com@herrie.org>
+
+description: |
+  PM8901 is a secondary PMIC paired with PM8058 on MSM8x60 family
+  (MSM8260/MSM8660/APQ8060) platforms. It exposes an over-temperature
+  alarm block at SSBI offset
+  0x23 (CTRL) / 0x24 (PWM) with four selectable thresholds and three
+  escalating stages. Unlike PM8058, there is no raw die-temperature
+  ADC channel — the driver decodes the stage + threshold pair into a
+  representative millicelsius value.
+
+  Two PMIC-internal interrupts are exposed: a stage-transition alarm
+  (TEMP_ALARM, PM8901 IRQ block 6 bit 4 == 52) and a hi-temp alarm
+  (TEMP_HI_ALARM, block 6 bit 5 == 53).
+
+  The driver registers a thermal-of sensor; board DTs declare trip
+  points and a critical-trip action against it.
+
+properties:
+  compatible:
+    const: qcom,pm8901-temp-alarm
+
+  reg:
+    description: SSBI offset of the temp-alarm CTRL register.
+    maxItems: 1
+
+  interrupts:
+    items:
+      - description: Stage-transition alarm interrupt (TEMP_ALARM).
+      - description: Hi-temperature alarm interrupt (TEMP_HI_ALARM).
+
+  interrupt-names:
+    items:
+      - const: alarm
+      - const: hi-alarm
+
+  "#thermal-sensor-cells":
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+  - "#thermal-sensor-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    ssbi {
+      pmic {
+        compatible = "qcom,pm8901";
+        #interrupt-cells = <2>;
+        interrupt-controller;
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        pm8901_temp: temp-alarm@23 {
+          compatible = "qcom,pm8901-temp-alarm";
+          reg = <0x23>;
+          interrupts = <52 IRQ_TYPE_EDGE_RISING>,
+                       <53 IRQ_TYPE_EDGE_RISING>;
+          interrupt-names = "alarm", "hi-alarm";
+          #thermal-sensor-cells = <0>;
+        };
+      };
+    };
-- 
2.43.0


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

* [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver
  2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
                   ` (3 preceding siblings ...)
  2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, linux-arm-msm, linux-kernel, linux-pm,
	Lukasz Luba, Rafael J. Wysocki, Rob Herring, Thara Gopinath,
	van Hazendonk, Zhang Rui

Add a thermal-of sensor driver for the temperature-alarm block inside
the Qualcomm PM8901 PMIC. PM8901 is a secondary PMIC paired with
PM8058 on the MSM8x60 family (MSM8260/MSM8660/APQ8060). It exposes
an over-temperature alarm at SSBI offset 0x23/0x24 with three
escalating stages (105/125/145 C); the driver decodes the stage +
threshold pair into a millicelsius reading and registers two PMIC-
internal interrupts (TEMP_ALARM at block 6 bit 4, TEMP_HI_ALARM at
block 6 bit 5).

Used by board thermal-zones for the orderly_poweroff path on the HP
TouchPad.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/thermal/qcom/Kconfig          |  12 +
 drivers/thermal/qcom/Makefile         |   1 +
 drivers/thermal/qcom/qcom-pm8901-tm.c | 341 ++++++++++++++++++++++++++
 3 files changed, 354 insertions(+)
 create mode 100644 drivers/thermal/qcom/qcom-pm8901-tm.c

diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index a6bb01082ec6..af099032f1e6 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -32,6 +32,18 @@ config QCOM_SPMI_TEMP_ALARM
 	  real time die temperature if an ADC is present or an estimate of the
 	  temperature based upon the over temperature stage value.
 
+config QCOM_PM8901_TEMP_ALARM
+	tristate "Qualcomm PM8901 PMIC Temperature Alarm"
+	depends on MFD_PM8XXX || COMPILE_TEST
+	depends on THERMAL_OF
+	help
+	  This enables the thermal driver for the PM8901 PMIC over-temperature
+	  alarm block. PM8901 exposes a stage-based alarm (no raw ADC) with
+	  four selectable thresholds and three escalating stages. The driver
+	  registers a thermal-of sensor so a board device tree can declare
+	  trip points and a critical-trip action (orderly_poweroff). Used on
+	  HP TouchPad (APQ8060) where PM8901 supplies the secondary PMIC die.
+
 config QCOM_LMH
 	tristate "Qualcomm Limits Management Hardware"
 	depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index 0fa2512042e7..90dc05151e33 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -5,4 +5,5 @@ qcom_tsens-y			+= tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
 				   tsens-8960.o
 obj-$(CONFIG_QCOM_SPMI_ADC_TM5)	+= qcom-spmi-adc-tm5.o
 obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
+obj-$(CONFIG_QCOM_PM8901_TEMP_ALARM)	+= qcom-pm8901-tm.o
 obj-$(CONFIG_QCOM_LMH)		+= lmh.o
diff --git a/drivers/thermal/qcom/qcom-pm8901-tm.c b/drivers/thermal/qcom/qcom-pm8901-tm.c
new file mode 100644
index 000000000000..d174d6897921
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-pm8901-tm.c
@@ -0,0 +1,341 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Qualcomm PM8901 PMIC Thermal-Alarm Driver
+ *
+ * Mainline port of the legacy 2.6.35-palm drivers/thermal/pmic8901-tm.c.
+ * PM8901 exposes a stage-based over-temperature alarm (no raw ADC) with
+ * four selectable thresholds and three escalating stages. This driver
+ * mirrors the legacy programming exactly (threshold-set 0, software
+ * override enabled, PWM gating at 8 Hz) and registers a thermal-of
+ * sensor so a board DT can declare trip points and a critical action.
+ *
+ * Copyright (c) 2010-2011, Code Aurora Forum.
+ * Copyright (c) 2026, HP TouchPad mainline port.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+/* SSBI registers (offsets from the per-instance reg base) */
+#define PM8901_TM_REG_CTRL		0x00	/* CTRL/STATUS  (base + 0) */
+#define PM8901_TM_REG_PWM		0x01	/* PWM gating  (base + 1) */
+
+/* CTRL register fields */
+#define CTRL_ST3_SD			BIT(7)
+#define CTRL_ST2_SD			BIT(6)
+#define CTRL_STATUS_MASK		GENMASK(5, 4)
+#define CTRL_THRESH_MASK		GENMASK(3, 2)
+#define CTRL_OVRD_ST3			BIT(1)
+#define CTRL_OVRD_ST2			BIT(0)
+#define CTRL_OVRD_MASK			GENMASK(1, 0)
+
+/* PWM register fields */
+#define PWM_EN				BIT(7)
+#define PWM_PER_PRE_MASK		GENMASK(5, 3)
+#define PWM_PER_DIV_MASK		GENMASK(2, 0)
+
+/* Temperature math (from legacy pmic8901-tm.c) */
+#define PM8901_TEMP_STAGE_STEP		20000	/* 20 deg C between stages */
+#define PM8901_TEMP_STAGE_HYSTERESIS	2000	/*  2 deg C transition guard */
+#define PM8901_TEMP_THRESH_MIN		105000	/* threshold 0 base = 105 C */
+#define PM8901_TEMP_THRESH_STEP		5000	/*  5 deg C per threshold step */
+
+/*
+ * PM8901 has no real die ADC; when stage == 0 ("below threshold") we
+ * report a plausible idle estimate matching the legacy DEFAULT_NO_ADC_TEMP.
+ */
+#define PM8901_TEMP_NO_ALARM		37000
+
+struct pm8901_tm_chip {
+	struct device			*dev;
+	struct regmap			*map;
+	struct thermal_zone_device	*tz_dev;
+	struct mutex			lock;
+	unsigned int			base;	/* SSBI offset, from DT reg */
+	unsigned int			stage;
+	unsigned int			thresh;
+	int				temp;
+	bool				initialised;
+};
+
+static int pm8901_tm_read_ctrl(struct pm8901_tm_chip *chip, u8 *val)
+{
+	unsigned int v;
+	int ret;
+
+	ret = regmap_read(chip->map, chip->base + PM8901_TM_REG_CTRL, &v);
+	if (!ret)
+		*val = v;
+	return ret;
+}
+
+static int pm8901_tm_write_ctrl(struct pm8901_tm_chip *chip, u8 val)
+{
+	return regmap_write(chip->map, chip->base + PM8901_TM_REG_CTRL, val);
+}
+
+static int pm8901_tm_write_pwm(struct pm8901_tm_chip *chip, u8 val)
+{
+	return regmap_write(chip->map, chip->base + PM8901_TM_REG_PWM, val);
+}
+
+/*
+ * Decode the (stage, threshold) pair into a single millicelsius value.
+ * Logic matches the legacy pmic8901-tm.c hysteresis selection:
+ *  - on a rising stage transition, use the lower bound of the new stage
+ *    plus +HYSTERESIS so we don't bounce
+ *  - on a falling stage transition, use the upper bound of the new stage
+ *    minus -HYSTERESIS
+ *  - on the first read after probe (initialised == false), pick a
+ *    representative point: midpoint of the stage range, or
+ *    PM8901_TEMP_NO_ALARM when stage == 0.
+ */
+static int pm8901_tm_update_temp_locked(struct pm8901_tm_chip *chip)
+{
+	unsigned int new_stage;
+	u8 reg;
+	int ret;
+
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (ret)
+		return ret;
+
+	new_stage = FIELD_GET(CTRL_STATUS_MASK, reg);
+	chip->thresh = FIELD_GET(CTRL_THRESH_MASK, reg);
+
+	if (!chip->initialised) {
+		if (new_stage)
+			chip->temp = PM8901_TEMP_THRESH_MIN +
+				     chip->thresh * PM8901_TEMP_THRESH_STEP +
+				     (new_stage - 1) * PM8901_TEMP_STAGE_STEP;
+		else
+			chip->temp = PM8901_TEMP_NO_ALARM;
+		chip->initialised = true;
+	} else if (new_stage > chip->stage) {
+		chip->temp = PM8901_TEMP_THRESH_MIN +
+			     chip->thresh * PM8901_TEMP_THRESH_STEP +
+			     (new_stage - 1) * PM8901_TEMP_STAGE_STEP +
+			     PM8901_TEMP_STAGE_HYSTERESIS;
+	} else if (new_stage < chip->stage) {
+		chip->temp = PM8901_TEMP_THRESH_MIN +
+			     chip->thresh * PM8901_TEMP_THRESH_STEP +
+			     new_stage * PM8901_TEMP_STAGE_STEP -
+			     PM8901_TEMP_STAGE_HYSTERESIS;
+	}
+
+	chip->stage = new_stage;
+	return 0;
+}
+
+static int pm8901_tm_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+	struct pm8901_tm_chip *chip = thermal_zone_device_priv(tz);
+	int ret;
+
+	if (!temp)
+		return -EINVAL;
+
+	mutex_lock(&chip->lock);
+	ret = pm8901_tm_update_temp_locked(chip);
+	if (!ret)
+		*temp = chip->temp;
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static const struct thermal_zone_device_ops pm8901_tm_zone_ops = {
+	.get_temp = pm8901_tm_get_temp,
+};
+
+/*
+ * Shared ISR for both TEMP_ALARM (stage-transition) and TEMP_HI_ALARM
+ * (hi-temp) interrupts. Updates the cached temperature, clears any
+ * latched ST2_SD / ST3_SD shutdown bits so the next stage transition
+ * can be observed, and pokes the thermal core which then re-reads
+ * temp and walks trips (a critical-trip cross triggers orderly_poweroff
+ * via the kernel's standard machinery).
+ */
+static irqreturn_t pm8901_tm_isr(int irq, void *data)
+{
+	struct pm8901_tm_chip *chip = data;
+	u8 reg;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = pm8901_tm_update_temp_locked(chip);
+	if (ret) {
+		mutex_unlock(&chip->lock);
+		return IRQ_HANDLED;
+	}
+
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (!ret && (reg & (CTRL_ST2_SD | CTRL_ST3_SD))) {
+		reg &= ~(CTRL_ST2_SD | CTRL_ST3_SD | CTRL_STATUS_MASK);
+		pm8901_tm_write_ctrl(chip, reg);
+	}
+
+	dev_dbg(chip->dev, "alarm irq=%d stage=%u thresh=%u temp=%d\n",
+		irq, chip->stage, chip->thresh, chip->temp);
+
+	mutex_unlock(&chip->lock);
+
+	thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Program PM8901 to the legacy default: threshold-set 0 (105 / 125 / 145 C),
+ * software override enabled (kernel handles shutdown, PMIC does not auto-cut),
+ * PWM at 8 Hz (legacy "cut down on unnecessary interrupts" rate).
+ */
+static int pm8901_tm_init_hw(struct pm8901_tm_chip *chip)
+{
+	int ret;
+	u8 reg;
+
+	mutex_lock(&chip->lock);
+
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (ret)
+		goto out;
+
+	/*
+	 * Enable software override so PMIC does NOT auto-shut-down on stage 3.
+	 * Critical-trip orderly_poweroff is delivered by the kernel thermal
+	 * core via the DT thermal-zone trip with type = "critical".
+	 */
+	reg = (reg & ~(CTRL_OVRD_MASK | CTRL_STATUS_MASK | CTRL_THRESH_MASK)) |
+	      CTRL_OVRD_ST3 | CTRL_OVRD_ST2;
+	ret = pm8901_tm_write_ctrl(chip, reg);
+	if (ret)
+		goto out;
+
+	chip->thresh = 0;
+
+	/* PWM @ 8 Hz: PWM_EN | PRE=3 | DIV=3 — verbatim from legacy. */
+	reg = PWM_EN | FIELD_PREP(PWM_PER_PRE_MASK, 3) |
+	      FIELD_PREP(PWM_PER_DIV_MASK, 3);
+	ret = pm8901_tm_write_pwm(chip, reg);
+	if (ret)
+		goto out;
+
+	/* Prime the cached temperature from current hardware state. */
+	chip->initialised = false;
+	ret = pm8901_tm_update_temp_locked(chip);
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int pm8901_tm_probe(struct platform_device *pdev)
+{
+	struct pm8901_tm_chip *chip;
+	int ret, irq_alarm, irq_hi_alarm;
+	u32 res;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = &pdev->dev;
+	mutex_init(&chip->lock);
+
+	chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!chip->map)
+		return dev_err_probe(&pdev->dev, -ENXIO,
+				     "no regmap on PM8901 parent\n");
+
+	ret = of_property_read_u32(pdev->dev.of_node, "reg", &res);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "missing reg property\n");
+	chip->base = res;
+
+	irq_alarm = platform_get_irq_byname(pdev, "alarm");
+	if (irq_alarm < 0)
+		return irq_alarm;
+	irq_hi_alarm = platform_get_irq_byname(pdev, "hi-alarm");
+	if (irq_hi_alarm < 0)
+		return irq_hi_alarm;
+
+	ret = pm8901_tm_init_hw(chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "hw init failed\n");
+
+	chip->tz_dev = devm_thermal_of_zone_register(&pdev->dev, 0, chip,
+						     &pm8901_tm_zone_ops);
+	if (IS_ERR(chip->tz_dev))
+		return dev_err_probe(&pdev->dev, PTR_ERR(chip->tz_dev),
+				     "thermal zone register failed\n");
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq_alarm, NULL,
+					pm8901_tm_isr, IRQF_ONESHOT,
+					"pm8901-tm-alarm", chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "alarm IRQ request failed\n");
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq_hi_alarm, NULL,
+					pm8901_tm_isr, IRQF_ONESHOT,
+					"pm8901-tm-hi-alarm", chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "hi-alarm IRQ request failed\n");
+
+	platform_set_drvdata(pdev, chip);
+	thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+	dev_info(&pdev->dev,
+		 "PM8901 thermal alarm: base=0x%x stage=%u thresh=%u temp=%d\n",
+		 chip->base, chip->stage, chip->thresh, chip->temp);
+
+	return 0;
+}
+
+static void pm8901_tm_remove(struct platform_device *pdev)
+{
+	struct pm8901_tm_chip *chip = platform_get_drvdata(pdev);
+	u8 reg;
+
+	/*
+	 * Disable software override on the way out so the PMIC reverts to
+	 * its hardware auto-cut behaviour if the kernel is no longer the
+	 * shutdown agent. Best-effort: ignore errors.
+	 */
+	mutex_lock(&chip->lock);
+	if (!pm8901_tm_read_ctrl(chip, &reg)) {
+		reg &= ~CTRL_OVRD_MASK;
+		pm8901_tm_write_ctrl(chip, reg);
+	}
+	mutex_unlock(&chip->lock);
+}
+
+static const struct of_device_id pm8901_tm_match_table[] = {
+	{ .compatible = "qcom,pm8901-temp-alarm" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pm8901_tm_match_table);
+
+static struct platform_driver pm8901_tm_driver = {
+	.driver = {
+		.name		= "pm8901-temp-alarm",
+		.of_match_table	= pm8901_tm_match_table,
+	},
+	.probe	= pm8901_tm_probe,
+	.remove	= pm8901_tm_remove,
+};
+module_platform_driver(pm8901_tm_driver);
+
+MODULE_ALIAS("platform:pm8901-temp-alarm");
+MODULE_DESCRIPTION("Qualcomm PM8901 PMIC Thermal Alarm driver");
+MODULE_LICENSE("GPL v2");
-- 
2.43.0


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

* Re: [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm
  2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
@ 2026-05-30 20:48   ` Rob Herring (Arm)
  0 siblings, 0 replies; 14+ messages in thread
From: Rob Herring (Arm) @ 2026-05-30 20:48 UTC (permalink / raw)
  To: Herman van Hazendonk
  Cc: linux-kernel, Thara Gopinath, Zhang Rui, linux-arm-msm, linux-pm,
	Conor Dooley, Rafael J. Wysocki, devicetree, Lukasz Luba,
	Daniel Lezcano, Amit Kucheria, Krzysztof Kozlowski


On Sat, 30 May 2026 16:00:39 +0200, Herman van Hazendonk wrote:
> Add the binding for the temperature-alarm block inside the Qualcomm
> PM8901 PMIC (companion to the PM8058 on MSM8x60). The driver exposes
> the PM8901 die-temperature trip stages (105 / 125 / 145 C) to the
> thermal framework via a thermal-zone, with stage 3 wired as a
> critical trip so the kernel issues orderly_poweroff() when the part
> overheats.
> 
> The binding describes the SSBI sub-node address, the GIC interrupt
> the alarm raises on a stage transition, and the parent PMIC
> reference.
> 
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
>  .../thermal/qcom,pm8901-temp-alarm.yaml       | 79 +++++++++++++++++++
>  1 file changed, 79 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
> 

My bot found errors running 'make dt_binding_check' on your patch:

yamllint warnings/errors:

dtschema/dtc warnings/errors:
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.example.dtb: pmic (qcom,pm8901): 'temp-alarm@23' does not match any of the regexes: '^pinctrl-[0-9]+$', 'gpio@[0-9a-f]+$', 'keypad@[0-9a-f]+$', 'led@[0-9a-f]+$', 'mpps@[0-9a-f]+$', 'pwrkey@[0-9a-f]+$', 'rtc@[0-9a-f]+$', 'vibrator@[0-9a-f]+$', 'xoadc@[0-9a-f]+$'
	from schema $id: http://devicetree.org/schemas/mfd/qcom-pm8xxx.yaml
/builds/robherring/dt-review-ci/linux/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.example.dtb: pmic (qcom,pm8901): 'oneOf' conditional failed, one must be fixed:
	'interrupts' is a required property
	'interrupts-extended' is a required property
	from schema $id: http://devicetree.org/schemas/mfd/qcom-pm8xxx.yaml

doc reference errors (make refcheckdocs):

See https://patchwork.kernel.org/project/devicetree/patch/386f3cef9d50d61f588f99706d9f979c56f8bab8.1780148149.git.github.com@herrie.org

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.


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

* [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
	linux-arm-msm, linux-kernel, linux-pm, Rob Herring

Hi all,

Self-review (with Sashiko AI assist) caught five real issues in v1's
NoC driver before the maintainer review cycle reached them, so
re-rolling promptly. v1:

  https://lore.kernel.org/linux-arm-msm/cover.1780148149.git.github.com@herrie.org/

v2 changes (driver patch only; the binding header is unchanged from v1):

  - MMFAB master port collision: mmfab_to_appss claimed port 2, but
    that port was already owned by mmfab_mas_adm1_port0. Per the
    fabric's port table the APPS_FAB gateway lives at port 11; fix
    the DEFINE_QNODE so ADM1 DMA's arbitration vote on MMFAB is no
    longer overwritten by the gateway.

  - msm8660_rpm_commit() double-converted bandwidth units: it called
    icc_units_to_bps() (which already returns bytes/s) and then
    immediately divided by 8 "bits -> bytes", asking RPM for 1/8 of
    the bandwidth that consumers had requested. Drop the spurious
    divide.

  - Fabric rate cache hoisted to provider scope and divided by the
    fabric's own bus width rather than the triggering node's local
    buswidth. A fabric has one shared hardware clock; using each
    master's local buswidth makes the requested clk rate oscillate
    depending on which master called icc_set_bw() last, and caching
    the result on the node lets a subsequent update skip clk_set_rate
    even though a different node has already moved the hardware. The
    new layout adds desc->bus_width per fabric (AFAB/SFAB/DFAB=8,
    MMFAB=16) and qp->rate as the single source of truth.

  - msm8660_get_rpm() rewritten:
      * returns ERR_PTR(-EPROBE_DEFER) when the RPM phandle resolves
        but the device hasn't probed yet, instead of returning NULL
        and silently disabling ARB forever;
      * adds a device_link to the RPM device so the devres-managed
        qcom_rpm struct can't be freed out from under us if the RPM
        driver is unbound at runtime (previously a use-after-free
        risk because we kept a pointer past put_device()).
    Probe propagates IS_ERR(qp->rpm) up so the framework retries.

  - devm_clk_bulk_get_optional() now distinguishes -EPROBE_DEFER
    (propagated) from real "no clocks" (continue without scaling).
    Previously every error was swallowed, which permanently disabled
    clock scaling if the icc driver happened to probe before the
    clock provider - which it usually does, given the core_initcall
    ordering.

No functional change intended on the working paths; this is purely
correctness work.

Thanks,
Herman

Herman van Hazendonk (2):
  dt-bindings: interconnect: qcom: add msm8660 fabric IDs
  interconnect: qcom: add MSM8x60 NoC driver

 drivers/interconnect/qcom/Kconfig             |   10 +
 drivers/interconnect/qcom/Makefile            |    2 +
 drivers/interconnect/qcom/msm8660.c           | 1069 +++++++++++++++++
 .../dt-bindings/interconnect/qcom,msm8660.h   |  156 +++
 4 files changed, 1237 insertions(+)
 create mode 100644 drivers/interconnect/qcom/msm8660.c
 create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h

-- 
2.43.0


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

* [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
                     ` (3 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
	linux-arm-msm, linux-kernel, linux-pm, Rob Herring

Add the dt-binding interconnect master/slave ID header for the
MSM8x60 family (MSM8260/MSM8660/APQ8060) fabric mesh. The chip's
NoC fabric is split into multiple sub-fabrics that the qnoc-msm8660
driver models:

  AFAB  - Applications fabric (Scorpion CPU + L2)
  SFAB  - System fabric (DMA, SPS, security)
  MMFAB - Multimedia fabric (MDP, GPU, camera, video, rotator)
  DFAB  - Daytona fabric (SDC, ADM master/slave)
  SFPB  - System Fast Peripheral Bridge (RPM, MPM, PMIC SSBI)
  CFPB  - CPU Subsystem Fast Peripheral Bus (GSBI UART/QUP, USB FS,
          TSIF, TSSC, PDM, PRNG)

IDs derived from the legacy webOS msm_bus_board_8660.c master/slave
enums, normalised to the upstream interconnect-framework naming.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 .../dt-bindings/interconnect/qcom,msm8660.h   | 156 ++++++++++++++++++
 1 file changed, 156 insertions(+)
 create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h

diff --git a/include/dt-bindings/interconnect/qcom,msm8660.h b/include/dt-bindings/interconnect/qcom,msm8660.h
new file mode 100644
index 000000000000..c9ce3f5a5276
--- /dev/null
+++ b/include/dt-bindings/interconnect/qcom,msm8660.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) interconnect IDs
+ *
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Based on webOS kernel msm_bus_board_8660.c
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ */
+
+#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H
+#define __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H
+
+/*
+ * MSM8x60 has a fabric-based bus architecture:
+ * - APPSS Fabric: CPU and memory interface
+ * - System Fabric: System peripherals and DMA
+ * - MMSS Fabric: Multimedia subsystem (display, camera, video)
+ * - Daytona Fabric: Peripheral bus (SDCC, ADM DMA)
+ * - System FPB: System Fast Peripheral Bus
+ * - CPSS FPB: CPU Subsystem Fast Peripheral Bus
+ */
+
+/* APPSS Fabric - Apps processor fabric */
+#define AFAB_MAS_AMPSS_M0		0
+#define AFAB_MAS_AMPSS_M1		1
+#define AFAB_SLV_EBI_CH0		2
+#define AFAB_SLV_AMPSS_L2		3
+#define AFAB_TO_MMSS			4
+#define AFAB_TO_SYSTEM			5
+
+/* System Fabric - System bus */
+#define SFAB_MAS_APPSS			0
+#define SFAB_MAS_SPS			1
+#define SFAB_MAS_ADM0_PORT0		2
+#define SFAB_MAS_ADM0_PORT1		3
+#define SFAB_MAS_ADM1_PORT0		4
+#define SFAB_MAS_ADM1_PORT1		5
+#define SFAB_MAS_LPASS_PROC		6
+#define SFAB_MAS_MSS_PROCI		7
+#define SFAB_MAS_MSS_PROCD		8
+#define SFAB_MAS_MSS_MDM_PORT0		9
+#define SFAB_MAS_LPASS			10
+#define SFAB_MAS_MMSS_FPB		11
+#define SFAB_MAS_ADM1_CI		12
+#define SFAB_MAS_ADM0_CI		13
+#define SFAB_MAS_MSS_MDM_PORT1		14
+#define SFAB_MAS_USB_HS			15
+#define SFAB_TO_APPSS			16
+#define SFAB_TO_SYSTEM_FPB		17
+#define SFAB_TO_CPSS_FPB		18
+#define SFAB_SLV_SPS			19
+#define SFAB_SLV_SYSTEM_IMEM		20
+#define SFAB_SLV_AMPSS			21
+#define SFAB_SLV_MSS			22
+#define SFAB_SLV_LPASS			23
+#define SFAB_SLV_MMSS_FPB		24
+#define SFAB_TO_DFAB			25
+
+/* MMSS Fabric - Multimedia subsystem */
+#define MMFAB_MAS_MDP_PORT0		0
+#define MMFAB_MAS_MDP_PORT1		1
+#define MMFAB_MAS_ADM1_PORT0		2
+#define MMFAB_MAS_ROTATOR		3
+#define MMFAB_MAS_GRAPHICS_3D		4
+#define MMFAB_MAS_JPEG_DEC		5
+#define MMFAB_MAS_GRAPHICS_2D_CORE0	6
+#define MMFAB_MAS_VFE			7
+#define MMFAB_MAS_VPE			8
+#define MMFAB_MAS_JPEG_ENC		9
+#define MMFAB_MAS_GRAPHICS_2D_CORE1	10
+#define MMFAB_MAS_HD_CODEC_PORT0	11
+#define MMFAB_MAS_HD_CODEC_PORT1	12
+#define MMFAB_TO_APPSS			13
+#define MMFAB_SLV_SMI			14
+#define MMFAB_SLV_MM_IMEM		15
+
+/*
+ * Daytona Fabric (DFAB) - Peripheral bus
+ *
+ * DFAB connects slower peripherals (SDCC, ADM DMA) to the system fabric.
+ * The webOS kernel managed DFAB bandwidth via voter clocks (dfab_sdc*_clk,
+ * dfab_usb_hs_clk). In mainline, this is handled by the interconnect framework.
+ *
+ * USB HS is included as a DFAB voter for compatibility with the legacy clock
+ * voting mechanism. The webOS kernel comment said: "if usb link is in sps
+ * there is no need for usb pclk as daytona fabric clock will be used instead".
+ * This keeps DFAB clock stable when USB is active.
+ */
+#define DFAB_MAS_SDC1			0
+#define DFAB_MAS_SDC2			1
+#define DFAB_MAS_SDC3			2
+#define DFAB_MAS_SDC4			3
+#define DFAB_MAS_SDC5			4
+#define DFAB_MAS_ADM0_MASTER		5
+#define DFAB_MAS_ADM1_MASTER		6
+#define DFAB_TO_SFAB			7
+#define DFAB_SLV_SDC1			8
+#define DFAB_SLV_SDC2			9
+#define DFAB_SLV_SDC3			10
+#define DFAB_SLV_SDC4			11
+#define DFAB_SLV_SDC5			12
+#define DFAB_MAS_USB_HS			13
+#define DFAB_MAS_DSPS			14
+
+/* System FPB - Slow peripheral bus for system */
+#define SFPB_MAS_SYSTEM			0
+#define SFPB_MAS_SPDM			1
+#define SFPB_MAS_RPM			2
+#define SFPB_SLV_SPDM			3
+#define SFPB_SLV_RPM			4
+#define SFPB_SLV_RPM_MSG_RAM		5
+#define SFPB_SLV_MPM			6
+#define SFPB_SLV_PMIC1_SSBI1_A		7
+#define SFPB_SLV_PMIC1_SSBI1_B		8
+#define SFPB_SLV_PMIC1_SSBI1_C		9
+#define SFPB_SLV_PMIC2_SSBI2_A		10
+#define SFPB_SLV_PMIC2_SSBI2_B		11
+
+/* CPSS FPB - CPU subsystem fast peripheral bus */
+#define CFPB_MAS_SYSTEM			0
+#define CFPB_SLV_GSBI1_UART		1
+#define CFPB_SLV_GSBI2_UART		2
+#define CFPB_SLV_GSBI3_UART		3
+#define CFPB_SLV_GSBI4_UART		4
+#define CFPB_SLV_GSBI5_UART		5
+#define CFPB_SLV_GSBI6_UART		6
+#define CFPB_SLV_GSBI7_UART		7
+#define CFPB_SLV_GSBI8_UART		8
+#define CFPB_SLV_GSBI9_UART		9
+#define CFPB_SLV_GSBI10_UART		10
+#define CFPB_SLV_GSBI11_UART		11
+#define CFPB_SLV_GSBI12_UART		12
+#define CFPB_SLV_GSBI1_QUP		13
+#define CFPB_SLV_GSBI2_QUP		14
+#define CFPB_SLV_GSBI3_QUP		15
+#define CFPB_SLV_GSBI4_QUP		16
+#define CFPB_SLV_GSBI5_QUP		17
+#define CFPB_SLV_GSBI6_QUP		18
+#define CFPB_SLV_GSBI7_QUP		19
+#define CFPB_SLV_GSBI8_QUP		20
+#define CFPB_SLV_GSBI9_QUP		21
+#define CFPB_SLV_GSBI10_QUP		22
+#define CFPB_SLV_GSBI11_QUP		23
+#define CFPB_SLV_GSBI12_QUP		24
+#define CFPB_SLV_EBI2_NAND		25
+#define CFPB_SLV_USB_FS1		26
+#define CFPB_SLV_USB_FS2		27
+#define CFPB_SLV_TSIF			28
+#define CFPB_SLV_MSM_TSSC		29
+#define CFPB_SLV_MSM_PDM		30
+#define CFPB_SLV_MSM_DIMEM		31
+#define CFPB_SLV_MSM_TCSR		32
+#define CFPB_SLV_MSM_PRNG		33
+
+#endif /* __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H */
-- 
2.43.0


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

* [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode Herman van Hazendonk
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
	linux-arm-msm, linux-kernel, linux-pm, Rob Herring

Add a Qualcomm interconnect driver for the MSM8x60 family
(MSM8260/MSM8660/APQ8060), modelling the four fabrics that connect
masters and slaves on these Scorpion-class SoCs:

  - AFAB  : Application/CPU fabric
  - SFAB  : System fabric (peripherals, USB, SDCC, etc.)
  - MMFAB : Multimedia fabric (MDP, VFE, VIDC, GPU, JPEG, VPE, ROT)
  - DFAB  : Daytona fabric (low-bandwidth peripherals)

The driver implements the interconnect-provider API so that consumer
drivers (display, camera, video, GPU, USB, MMC) can request bandwidth
between specific masters and slaves via icc_set_bw(), letting the
firmware-managed bus-scaling decide actual NoC clock rates.

Used on the HP TouchPad (Tenderloin) and on the Pre3 / Veer
form-factors; without it, the multimedia and storage paths are
starved of bandwidth and run at minimum NoC clocks.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/interconnect/qcom/Kconfig   |   10 +
 drivers/interconnect/qcom/Makefile  |    2 +
 drivers/interconnect/qcom/msm8660.c | 1069 +++++++++++++++++++++++++++
 3 files changed, 1081 insertions(+)
 create mode 100644 drivers/interconnect/qcom/msm8660.c

diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig
index 786b4eda44b4..17364be522c7 100644
--- a/drivers/interconnect/qcom/Kconfig
+++ b/drivers/interconnect/qcom/Kconfig
@@ -80,6 +80,16 @@ config INTERCONNECT_QCOM_MSM8953
 	  This is a driver for the Qualcomm Network-on-Chip on msm8953-based
 	  platforms.
 
+config INTERCONNECT_QCOM_MSM8660
+	tristate "Qualcomm MSM8x60 interconnect driver"
+	depends on INTERCONNECT_QCOM
+	depends on MFD_QCOM_RPM
+	help
+	  This is a driver for the Qualcomm fabric-based bus interconnect
+	  on MSM8x60 family (MSM8260/MSM8660/APQ8060) platforms (e.g., HP TouchPad).
+	  The driver manages APPSS, System, and MMSS fabrics and sends
+	  per-port bandwidth arbitration requests to RPM firmware.
+
 config INTERCONNECT_QCOM_MSM8974
 	tristate "Qualcomm MSM8974 interconnect driver"
 	depends on INTERCONNECT_QCOM
diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile
index cdf2c6c9fbf3..0c849c30a907 100644
--- a/drivers/interconnect/qcom/Makefile
+++ b/drivers/interconnect/qcom/Makefile
@@ -13,6 +13,7 @@ qnoc-msm8916-objs			:= msm8916.o
 qnoc-msm8937-objs			:= msm8937.o
 qnoc-msm8939-objs			:= msm8939.o
 qnoc-msm8953-objs			:= msm8953.o
+qnoc-msm8660-objs			:= msm8660.o
 qnoc-msm8974-objs			:= msm8974.o
 qnoc-msm8976-objs			:= msm8976.o
 qnoc-msm8996-objs			:= msm8996.o
@@ -58,6 +59,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8937) += qnoc-msm8937.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8939) += qnoc-msm8939.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8953) += qnoc-msm8953.o
+obj-$(CONFIG_INTERCONNECT_QCOM_MSM8660) += qnoc-msm8660.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8976) += qnoc-msm8976.o
 obj-$(CONFIG_INTERCONNECT_QCOM_MSM8996) += qnoc-msm8996.o
diff --git a/drivers/interconnect/qcom/msm8660.c b/drivers/interconnect/qcom/msm8660.c
new file mode 100644
index 000000000000..174bc59da74f
--- /dev/null
+++ b/drivers/interconnect/qcom/msm8660.c
@@ -0,0 +1,1069 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) interconnect driver
+ *
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Based on msm8974.c by Brian Masney <masneyb@onstation.org>
+ * and webOS kernel msm_bus_board_8660.c / msm_bus_fabric.c
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * MSM8x60 has a fabric-based bus architecture:
+ *
+ *              +------------------+
+ *              |   APPSS Fabric   |  (CPU, L2, Memory)
+ *              +--------+---------+
+ *                       |
+ *         +-------------+-------------+
+ *         |                           |
+ *  +------+------+            +-------+-------+
+ *  | MMSS Fabric |            | System Fabric |
+ *  | (Display,   |            | (Peripherals, |
+ *  |  Camera,    |            |  DMA, etc)    |
+ *  |  Video)     |            +-------+-------+
+ *  +-------------+                    |
+ *                           +---------+---------+
+ *                           |                   |
+ *                    +------+------+     +------+------+
+ *                    | System FPB  |     |  CPSS FPB   |
+ *                    | (RPM, PMIC) |     | (GSBI, USB) |
+ *                    +-------------+     +-------------+
+ *
+ * Each fabric has an RPM arbitration interface that programs per-port
+ * bandwidth and priority tier via MM_FABRIC_ARB / SYS_FABRIC_ARB /
+ * APPS_FABRIC_ARB registers.  The webOS kernel sent these as packed
+ * u16 arrays (bwsum + arb) through msm_rpm_set().  This driver uses
+ * the mainline qcom_rpm_write() interface to do the same.
+ */
+
+#include <dt-bindings/interconnect/qcom,msm8660.h>
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+#include <linux/args.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interconnect-provider.h>
+#include <linux/io.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* Internal node IDs - these map to the DT binding IDs plus fabric offset */
+enum {
+	/* APPSS Fabric nodes */
+	MSM8660_AFAB_MAS_AMPSS_M0 = 1,
+	MSM8660_AFAB_MAS_AMPSS_M1,
+	MSM8660_AFAB_SLV_EBI_CH0,
+	MSM8660_AFAB_SLV_AMPSS_L2,
+	MSM8660_AFAB_TO_MMSS,
+	MSM8660_AFAB_TO_SYSTEM,
+
+	/* System Fabric nodes */
+	MSM8660_SFAB_MAS_APPSS,
+	MSM8660_SFAB_MAS_SPS,
+	MSM8660_SFAB_MAS_ADM0_PORT0,
+	MSM8660_SFAB_MAS_ADM0_PORT1,
+	MSM8660_SFAB_MAS_ADM1_PORT0,
+	MSM8660_SFAB_MAS_ADM1_PORT1,
+	MSM8660_SFAB_MAS_LPASS_PROC,
+	MSM8660_SFAB_MAS_MSS_PROCI,
+	MSM8660_SFAB_MAS_MSS_PROCD,
+	MSM8660_SFAB_MAS_MSS_MDM_PORT0,
+	MSM8660_SFAB_MAS_LPASS,
+	MSM8660_SFAB_MAS_MMSS_FPB,
+	MSM8660_SFAB_MAS_ADM1_CI,
+	MSM8660_SFAB_MAS_ADM0_CI,
+	MSM8660_SFAB_MAS_MSS_MDM_PORT1,
+	MSM8660_SFAB_MAS_USB_HS,
+	MSM8660_SFAB_TO_APPSS,
+	MSM8660_SFAB_TO_SYSTEM_FPB,
+	MSM8660_SFAB_TO_CPSS_FPB,
+	MSM8660_SFAB_SLV_SPS,
+	MSM8660_SFAB_SLV_SYSTEM_IMEM,
+	MSM8660_SFAB_SLV_AMPSS,
+	MSM8660_SFAB_SLV_MSS,
+	MSM8660_SFAB_SLV_LPASS,
+	MSM8660_SFAB_SLV_MMSS_FPB,
+	MSM8660_SFAB_TO_DFAB,
+
+	/* Daytona Fabric nodes (DFAB) - peripheral bus */
+	MSM8660_DFAB_MAS_SDC1,
+	MSM8660_DFAB_MAS_SDC2,
+	MSM8660_DFAB_MAS_SDC3,
+	MSM8660_DFAB_MAS_SDC4,
+	MSM8660_DFAB_MAS_SDC5,
+	MSM8660_DFAB_MAS_ADM0_MASTER,
+	MSM8660_DFAB_MAS_ADM1_MASTER,
+	MSM8660_DFAB_TO_SFAB,
+	MSM8660_DFAB_SLV_SDC1,
+	MSM8660_DFAB_SLV_SDC2,
+	MSM8660_DFAB_SLV_SDC3,
+	MSM8660_DFAB_SLV_SDC4,
+	MSM8660_DFAB_SLV_SDC5,
+	MSM8660_DFAB_MAS_USB_HS,	/* USB HS DFAB voter */
+	MSM8660_DFAB_MAS_DSPS,		/* DSPS DFAB voter */
+
+	/* MMSS Fabric nodes */
+	MSM8660_MMFAB_MAS_MDP_PORT0,
+	MSM8660_MMFAB_MAS_MDP_PORT1,
+	MSM8660_MMFAB_MAS_ADM1_PORT0,
+	MSM8660_MMFAB_MAS_ROTATOR,
+	MSM8660_MMFAB_MAS_GRAPHICS_3D,
+	MSM8660_MMFAB_MAS_JPEG_DEC,
+	MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE0,
+	MSM8660_MMFAB_MAS_VFE,
+	MSM8660_MMFAB_MAS_VPE,
+	MSM8660_MMFAB_MAS_JPEG_ENC,
+	MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE1,
+	MSM8660_MMFAB_MAS_HD_CODEC_PORT0,
+	MSM8660_MMFAB_MAS_HD_CODEC_PORT1,
+	MSM8660_MMFAB_TO_APPSS,
+	MSM8660_MMFAB_SLV_SMI,
+	MSM8660_MMFAB_SLV_MM_IMEM,
+};
+
+#define to_msm8660_icc_provider(_provider) \
+	container_of(_provider, struct msm8660_icc_provider, provider)
+
+/*
+ * Minimum fabric clock rate to prevent bus starvation.
+ *
+ * When no consumers request bandwidth, the rate calculation yields 0,
+ * causing fabric clocks to drop to minimum. This creates bimodal
+ * performance: fast when other subsystems (like display) happen to
+ * request bandwidth, slow otherwise.
+ *
+ * 384 MHz keeps fabric fast during concurrent MDP display scanout
+ * and USB gadget traffic. webOS docs: "AXI bus frequency needs to be
+ * kept at maximum value while USB data transfers are happening."
+ * 266 MHz was insufficient - USB crashed during display activity.
+ */
+#define MSM8660_FABRIC_MIN_RATE		384000000UL	/* 384 MHz */
+
+/*
+ * Maximum RPM ARB buffer size across all fabrics.
+ * MM fabric is largest at 23 u32 words.
+ */
+#define MSM8660_MAX_RPM_BUF		23
+
+/*
+ * RPM fabric arbitration data format (from webOS msm_bus_fabric.c):
+ *
+ * Each u16 arb entry: bit 15 = tier (1=TIER1 high priority), bits 14-0 = BW
+ * Bandwidth is in 128KB units (bytes >> 17).
+ * Two u16 values are packed into each u32 RPM register word.
+ *
+ * Buffer layout: [bwsum pairs] [arb pairs]
+ * bwsum[slave_port] = total bandwidth to that slave
+ * arb[(tier-1)*nmasters + master_port] = per-master arbitration entry
+ */
+#define ARB_BWMASK		0x7FFF
+#define ARB_TIERMASK		0x8000
+#define ARB_TIER1		1
+#define ARB_TIER2		2
+
+static const struct clk_bulk_data msm8660_afab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+	{ .id = "ebi1" },
+	{ .id = "ebi1_a" },
+};
+
+static const struct clk_bulk_data msm8660_sfab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+};
+
+static const struct clk_bulk_data msm8660_mmfab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+	{ .id = "smi" },
+	{ .id = "smi_a" },
+};
+
+static const struct clk_bulk_data msm8660_dfab_clocks[] = {
+	{ .id = "bus" },
+	{ .id = "bus_a" },
+};
+
+/**
+ * struct msm8660_icc_node - MSM8660 specific interconnect nodes
+ * @name: the node name used in debugfs
+ * @id: a unique node identifier
+ * @links: an array of nodes where we can go next while traversing
+ * @num_links: the total number of @links
+ * @buswidth: width of the interconnect between a node and the bus (bytes)
+ * @rate: current bus clock rate in Hz
+ * @mas_port: master port index for RPM ARB (-1 if not a master)
+ * @slv_port: slave port index for RPM bwsum (-1 if not a slave)
+ * @mas_tier: master priority tier (ARB_TIER1 or ARB_TIER2, 0 if N/A)
+ */
+struct msm8660_icc_node {
+	unsigned char *name;
+	u16 id;
+#define MSM8660_ICC_MAX_LINKS	3
+	u16 links[MSM8660_ICC_MAX_LINKS];
+	u16 num_links;
+	u16 buswidth;
+	s8 mas_port;
+	s8 slv_port;
+	u8 mas_tier;
+};
+
+/**
+ * struct msm8660_icc_desc - Fabric descriptor
+ * @nodes: array of node pointers
+ * @num_nodes: number of nodes
+ * @bus_clks: clock definitions
+ * @num_clks: number of clocks
+ * @rpm_resource: QCOM_RPM_*_FABRIC_ARB constant, or -1 for no ARB
+ * @nmasters: number of master ports in this fabric (for ARB array sizing)
+ * @nslaves: number of slave ports in this fabric (for bwsum array sizing)
+ * @ntieredslaves: number of tiered slaves (ARB rows)
+ * @default_tiered_slave: 1-based index of default tiered slave for masters
+ * @rpm_buf_size: number of u32 words for RPM write
+ * @bus_width: representative fabric bus width in bytes, used as the
+ *             divisor for translating aggregate bytes/sec into a single
+ *             clock rate that drives the whole fabric
+ */
+struct msm8660_icc_desc {
+	struct msm8660_icc_node * const *nodes;
+	size_t num_nodes;
+	const struct clk_bulk_data *bus_clks;
+	size_t num_clks;
+	int rpm_resource;
+	u8 nmasters;
+	u8 nslaves;
+	u8 ntieredslaves;
+	u8 default_tiered_slave;
+	u8 rpm_buf_size;
+	u8 bus_width;
+};
+
+/**
+ * struct msm8660_icc_provider - MSM8660 specific interconnect provider
+ * @provider: generic interconnect provider
+ * @bus_clks: the clk_bulk_data table of bus clocks
+ * @num_clks: the total number of clk_bulk_data entries
+ * @rpm: RPM handle for fabric arbitration writes
+ * @desc: fabric descriptor with RPM metadata
+ * @arb: pre-allocated arbitration array (nmasters * ntieredslaves u16 entries)
+ * @bwsum: pre-allocated bandwidth sum array (nslaves u16 entries)
+ * @rpm_buf: pre-allocated RPM write buffer (rpm_buf_size u32 entries)
+ * @rate: last clock rate applied to the fabric bus_clks, used as the
+ *        single source of truth for whether the rate actually needs to
+ *        be reprogrammed (per-node caching would desync when different
+ *        masters update at different times)
+ */
+struct msm8660_icc_provider {
+	struct icc_provider provider;
+	struct clk_bulk_data *bus_clks;
+	int num_clks;
+	struct qcom_rpm *rpm;
+	const struct msm8660_icc_desc *desc;
+	u16 *arb;
+	u16 *bwsum;
+	u32 *rpm_buf;
+	u32 rate;
+};
+
+/*
+ * Node definitions with RPM port mapping.
+ *
+ * DEFINE_QNODE(_name, _id, _buswidth, _mas_port, _slv_port, _tier, links...)
+ *
+ * _mas_port: master port index for RPM ARB array (-1 if not a master)
+ * _slv_port: slave port index for RPM bwsum array (-1 if not a slave)
+ * _tier: master priority tier (ARB_TIER1=1, ARB_TIER2=2, 0 if N/A)
+ */
+#define DEFINE_QNODE(_name, _id, _buswidth, _mas, _slv, _tier, ...)	\
+	static struct msm8660_icc_node _name = {			\
+		.name = #_name,						\
+		.id = _id,						\
+		.buswidth = _buswidth,					\
+		.num_links = COUNT_ARGS(__VA_ARGS__),			\
+		.links = { __VA_ARGS__ },				\
+		.mas_port = _mas,					\
+		.slv_port = _slv,					\
+		.mas_tier = _tier,					\
+	}
+
+/*
+ * =========================================================================
+ * APPSS Fabric nodes
+ *
+ * 4 masters, 4 slaves, 2 tiered slaves
+ * Master ports: SMPSS_M0=0, SMPSS_M1=1, FAB_MMSS=2, FAB_SYSTEM=3
+ * Slave ports:  EBI_CH0=0, SMPSS_L2=1, MMSS_FAB=2, SYSTEM_FAB=3
+ * Default target: tiered slave 1 (EBI_CH0)
+ * =========================================================================
+ */
+DEFINE_QNODE(mas_ampss_m0, MSM8660_AFAB_MAS_AMPSS_M0, 8, 0, -1, ARB_TIER2,
+	     MSM8660_AFAB_SLV_EBI_CH0, MSM8660_AFAB_TO_MMSS, MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(mas_ampss_m1, MSM8660_AFAB_MAS_AMPSS_M1, 8, 1, -1, ARB_TIER2,
+	     MSM8660_AFAB_SLV_EBI_CH0, MSM8660_AFAB_TO_MMSS, MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(slv_ebi_ch0, MSM8660_AFAB_SLV_EBI_CH0, 8, -1, 0, 0);
+DEFINE_QNODE(slv_ampss_l2, MSM8660_AFAB_SLV_AMPSS_L2, 8, -1, 1, 0);
+/*
+ * Gateway nodes need links to both the cross-fabric gateway AND the memory
+ * slave to enable cross-fabric paths. Without link to EBI_CH0, path_find()
+ * can't route from MMSS/System fabric masters to main memory.
+ *
+ * AFAB_TO_MMSS doubles as AFAB master port 2 (the FAB_MMSS master). MDP
+ * scanout and GPU traffic enter AFAB through this gateway. Mark it
+ * ARB_TIER1 so display/multimedia traffic keeps priority over CPU L2
+ * misses inside the APPSS fabric — without this, MDP TIER1 priority
+ * earned in MMFAB is dropped at the AFAB boundary and MDP fetches lose
+ * arbitration to CPU traffic, producing PRIMARY_INTF_UDERRUN.
+ */
+DEFINE_QNODE(afab_to_mmss, MSM8660_AFAB_TO_MMSS, 8, 2, 2, ARB_TIER1,
+	     MSM8660_MMFAB_TO_APPSS, MSM8660_AFAB_SLV_EBI_CH0);
+DEFINE_QNODE(afab_to_system, MSM8660_AFAB_TO_SYSTEM, 8, 3, 3, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS, MSM8660_AFAB_SLV_EBI_CH0);
+
+static struct msm8660_icc_node * const msm8660_afab_nodes[] = {
+	[AFAB_MAS_AMPSS_M0] = &mas_ampss_m0,
+	[AFAB_MAS_AMPSS_M1] = &mas_ampss_m1,
+	[AFAB_SLV_EBI_CH0] = &slv_ebi_ch0,
+	[AFAB_SLV_AMPSS_L2] = &slv_ampss_l2,
+	[AFAB_TO_MMSS] = &afab_to_mmss,
+	[AFAB_TO_SYSTEM] = &afab_to_system,
+};
+
+static const struct msm8660_icc_desc msm8660_afab = {
+	.nodes = msm8660_afab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_afab_nodes),
+	.bus_clks = msm8660_afab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_afab_clocks),
+	.rpm_resource = QCOM_RPM_APPS_FABRIC_ARB,
+	.nmasters = 4,
+	.nslaves = 4,
+	.ntieredslaves = 2,
+	.default_tiered_slave = 1,	/* EBI_CH0 */
+	.rpm_buf_size = 6,
+	.bus_width = 8,			/* 64-bit APPSS fabric datapath */
+};
+
+/*
+ * =========================================================================
+ * System Fabric nodes
+ *
+ * 17 masters, 9 slaves, 2 tiered slaves
+ * Master ports: see enum msm_bus_8660_master_ports_type in webOS
+ * Slave ports:  APPSS_FAB=0, SPS=1, SYSTEM_IMEM=2, SMPSS=3, MSS=4,
+ *               LPASS=5, CPSS_FPB=6, SYSTEM_FPB=7, MMSS_FPB=8
+ * Default target: tiered slave 1 (APPSS gateway)
+ * =========================================================================
+ */
+DEFINE_QNODE(sfab_mas_appss, MSM8660_SFAB_MAS_APPSS, 8, 0, -1, ARB_TIER2,
+	     MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(sfab_mas_sps, MSM8660_SFAB_MAS_SPS, 8, 1, -1, ARB_TIER2,
+	     MSM8660_SFAB_SLV_SPS);
+/*
+ * ADM DMA masters - route through SFAB_TO_APPSS to reach EBI memory.
+ * Path: ADM -> SFAB_TO_APPSS -> AFAB_TO_SYSTEM -> AFAB_SLV_EBI_CH0
+ * This enables proper EBI bandwidth voting for DMA operations.
+ */
+DEFINE_QNODE(sfab_mas_adm0_port0, MSM8660_SFAB_MAS_ADM0_PORT0, 8, 2, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm0_port1, MSM8660_SFAB_MAS_ADM0_PORT1, 8, 3, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm1_port0, MSM8660_SFAB_MAS_ADM1_PORT0, 8, 4, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm1_port1, MSM8660_SFAB_MAS_ADM1_PORT1, 8, 5, -1, ARB_TIER2,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_lpass_proc, MSM8660_SFAB_MAS_LPASS_PROC, 8, 6, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_proci, MSM8660_SFAB_MAS_MSS_PROCI, 8, 7, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_procd, MSM8660_SFAB_MAS_MSS_PROCD, 8, 8, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_mdm_port0, MSM8660_SFAB_MAS_MSS_MDM_PORT0, 8, 9, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_lpass, MSM8660_SFAB_MAS_LPASS, 8, 10, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mmss_fpb, MSM8660_SFAB_MAS_MMSS_FPB, 8, 13, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_adm1_ci, MSM8660_SFAB_MAS_ADM1_CI, 8, 14, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_adm0_ci, MSM8660_SFAB_MAS_ADM0_CI, 8, 15, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_mdm_port1, MSM8660_SFAB_MAS_MSS_MDM_PORT1, 8, 16, -1, ARB_TIER2);
+/* USB HS has no dedicated master port in webOS SFAB - bandwidth voting only */
+DEFINE_QNODE(sfab_mas_usb_hs, MSM8660_SFAB_MAS_USB_HS, 8, -1, -1, 0,
+	     MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_to_appss, MSM8660_SFAB_TO_APPSS, 8, -1, 0, 0,
+	     MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(sfab_to_system_fpb, MSM8660_SFAB_TO_SYSTEM_FPB, 4, -1, 7, 0);
+DEFINE_QNODE(sfab_to_cpss_fpb, MSM8660_SFAB_TO_CPSS_FPB, 4, -1, 6, 0);
+DEFINE_QNODE(sfab_slv_sps, MSM8660_SFAB_SLV_SPS, 8, -1, 1, 0);
+DEFINE_QNODE(sfab_slv_system_imem, MSM8660_SFAB_SLV_SYSTEM_IMEM, 8, -1, 2, 0);
+DEFINE_QNODE(sfab_slv_ampss, MSM8660_SFAB_SLV_AMPSS, 8, -1, 3, 0);
+DEFINE_QNODE(sfab_slv_mss, MSM8660_SFAB_SLV_MSS, 8, -1, 4, 0);
+DEFINE_QNODE(sfab_slv_lpass, MSM8660_SFAB_SLV_LPASS, 8, -1, 5, 0);
+DEFINE_QNODE(sfab_slv_mmss_fpb, MSM8660_SFAB_SLV_MMSS_FPB, 8, -1, 8, 0);
+/*
+ * Gateway to DFAB: links to DFAB_TO_SFAB for path traversal.
+ * Also links to SFAB_TO_APPSS to enable DFAB->SFAB->AFAB->memory paths.
+ * No slave port in webOS SFAB config (DFAB is separate fabric).
+ */
+DEFINE_QNODE(sfab_to_dfab, MSM8660_SFAB_TO_DFAB, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB, MSM8660_SFAB_TO_APPSS);
+
+static struct msm8660_icc_node * const msm8660_sfab_nodes[] = {
+	[SFAB_MAS_APPSS] = &sfab_mas_appss,
+	[SFAB_MAS_SPS] = &sfab_mas_sps,
+	[SFAB_MAS_ADM0_PORT0] = &sfab_mas_adm0_port0,
+	[SFAB_MAS_ADM0_PORT1] = &sfab_mas_adm0_port1,
+	[SFAB_MAS_ADM1_PORT0] = &sfab_mas_adm1_port0,
+	[SFAB_MAS_ADM1_PORT1] = &sfab_mas_adm1_port1,
+	[SFAB_MAS_LPASS_PROC] = &sfab_mas_lpass_proc,
+	[SFAB_MAS_MSS_PROCI] = &sfab_mas_mss_proci,
+	[SFAB_MAS_MSS_PROCD] = &sfab_mas_mss_procd,
+	[SFAB_MAS_MSS_MDM_PORT0] = &sfab_mas_mss_mdm_port0,
+	[SFAB_MAS_LPASS] = &sfab_mas_lpass,
+	[SFAB_MAS_MMSS_FPB] = &sfab_mas_mmss_fpb,
+	[SFAB_MAS_ADM1_CI] = &sfab_mas_adm1_ci,
+	[SFAB_MAS_ADM0_CI] = &sfab_mas_adm0_ci,
+	[SFAB_MAS_MSS_MDM_PORT1] = &sfab_mas_mss_mdm_port1,
+	[SFAB_MAS_USB_HS] = &sfab_mas_usb_hs,
+	[SFAB_TO_APPSS] = &sfab_to_appss,
+	[SFAB_TO_SYSTEM_FPB] = &sfab_to_system_fpb,
+	[SFAB_TO_CPSS_FPB] = &sfab_to_cpss_fpb,
+	[SFAB_SLV_SPS] = &sfab_slv_sps,
+	[SFAB_SLV_SYSTEM_IMEM] = &sfab_slv_system_imem,
+	[SFAB_SLV_AMPSS] = &sfab_slv_ampss,
+	[SFAB_SLV_MSS] = &sfab_slv_mss,
+	[SFAB_SLV_LPASS] = &sfab_slv_lpass,
+	[SFAB_SLV_MMSS_FPB] = &sfab_slv_mmss_fpb,
+	[SFAB_TO_DFAB] = &sfab_to_dfab,
+};
+
+static const struct msm8660_icc_desc msm8660_sfab = {
+	.nodes = msm8660_sfab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_sfab_nodes),
+	.bus_clks = msm8660_sfab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_sfab_clocks),
+	.rpm_resource = QCOM_RPM_SYS_FABRIC_ARB,
+	.nmasters = 17,
+	.nslaves = 9,
+	.ntieredslaves = 2,
+	.default_tiered_slave = 1,	/* APPSS gateway */
+	.rpm_buf_size = 22,
+	.bus_width = 8,			/* 64-bit System fabric datapath */
+};
+
+/*
+ * =========================================================================
+ * MMSS Fabric nodes - Multimedia subsystem (MDP, camera, video, GPU)
+ *
+ * 14 masters, 4 slaves, 3 tiered slaves
+ * Master ports: MDP0=0, MDP1=1, ADM1=2, ROT=3, 3D=4, JPEG_DEC=5,
+ *               2D_CORE0=6, VFE=7, VPE=8, JPEG_ENC=9, 2D_CORE1=10,
+ *               (APPS_FAB=11), HD_CODEC0=12, HD_CODEC1=13
+ * Slave ports:  SMI=0, APPSS_FAB=1, (APPSS_FAB_1=2), MM_IMEM=3
+ * Tiered slaves: SMI=1, APPSS_FAB=2, MM_IMEM=3
+ * Default target: tiered slave 2 (APPSS gateway -> main memory)
+ *
+ * MDP ports get TIER1 (high priority) for guaranteed display refresh.
+ * All other masters get TIER2 (default priority).
+ * =========================================================================
+ */
+DEFINE_QNODE(mmfab_mas_mdp_port0, MSM8660_MMFAB_MAS_MDP_PORT0, 16, 0, -1, ARB_TIER1,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_mdp_port1, MSM8660_MMFAB_MAS_MDP_PORT1, 16, 1, -1, ARB_TIER1,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_adm1_port0, MSM8660_MMFAB_MAS_ADM1_PORT0, 8, 2, -1, ARB_TIER2);
+DEFINE_QNODE(mmfab_mas_rotator, MSM8660_MMFAB_MAS_ROTATOR, 16, 3, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_3d, MSM8660_MMFAB_MAS_GRAPHICS_3D, 16, 4, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_jpeg_dec, MSM8660_MMFAB_MAS_JPEG_DEC, 16, 5, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_2d_core0, MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE0, 16,
+	     6, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_vfe, MSM8660_MMFAB_MAS_VFE, 16, 7, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_vpe, MSM8660_MMFAB_MAS_VPE, 16, 8, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_jpeg_enc, MSM8660_MMFAB_MAS_JPEG_ENC, 16, 9, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_2d_core1, MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE1, 16,
+	     10, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_hd_codec_port0, MSM8660_MMFAB_MAS_HD_CODEC_PORT0, 16,
+	     12, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_hd_codec_port1, MSM8660_MMFAB_MAS_HD_CODEC_PORT1, 16,
+	     13, -1, ARB_TIER2,
+	     MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+/*
+ * Gateway from APPSS into MMSS: slave (port 1) for outbound traffic
+ * leaving MMSS, AND master (port 2) for inbound traffic arriving from
+ * APPSS (e.g. CPU memremap_wc accesses to SMI BOs). Without the master
+ * port and the forward links into MMFAB slaves (SMI / MM_IMEM), the
+ * ICC path-finder has no route from AMPSS_M0 to MMFAB_SLV_SMI; the
+ * cross-fabric gateway only worked for outbound traffic (MMSS masters
+ * reaching APPSS slaves like EBI).
+ *
+ * ARB_TIER1 keeps AMPSS->SMI traffic high-priority within MMFAB so
+ * CPU mmap reads/writes to SMI BOs don't get starved by MDP scanout.
+ */
+DEFINE_QNODE(mmfab_to_appss, MSM8660_MMFAB_TO_APPSS, 8, 11, 1, ARB_TIER1,
+	     MSM8660_AFAB_TO_MMSS,
+	     MSM8660_MMFAB_SLV_SMI,
+	     MSM8660_MMFAB_SLV_MM_IMEM);
+DEFINE_QNODE(mmfab_slv_smi, MSM8660_MMFAB_SLV_SMI, 16, -1, 0, 0);
+DEFINE_QNODE(mmfab_slv_mm_imem, MSM8660_MMFAB_SLV_MM_IMEM, 8, -1, 3, 0);
+
+static struct msm8660_icc_node * const msm8660_mmfab_nodes[] = {
+	[MMFAB_MAS_MDP_PORT0] = &mmfab_mas_mdp_port0,
+	[MMFAB_MAS_MDP_PORT1] = &mmfab_mas_mdp_port1,
+	[MMFAB_MAS_ADM1_PORT0] = &mmfab_mas_adm1_port0,
+	[MMFAB_MAS_ROTATOR] = &mmfab_mas_rotator,
+	[MMFAB_MAS_GRAPHICS_3D] = &mmfab_mas_graphics_3d,
+	[MMFAB_MAS_JPEG_DEC] = &mmfab_mas_jpeg_dec,
+	[MMFAB_MAS_GRAPHICS_2D_CORE0] = &mmfab_mas_graphics_2d_core0,
+	[MMFAB_MAS_VFE] = &mmfab_mas_vfe,
+	[MMFAB_MAS_VPE] = &mmfab_mas_vpe,
+	[MMFAB_MAS_JPEG_ENC] = &mmfab_mas_jpeg_enc,
+	[MMFAB_MAS_GRAPHICS_2D_CORE1] = &mmfab_mas_graphics_2d_core1,
+	[MMFAB_MAS_HD_CODEC_PORT0] = &mmfab_mas_hd_codec_port0,
+	[MMFAB_MAS_HD_CODEC_PORT1] = &mmfab_mas_hd_codec_port1,
+	[MMFAB_TO_APPSS] = &mmfab_to_appss,
+	[MMFAB_SLV_SMI] = &mmfab_slv_smi,
+	[MMFAB_SLV_MM_IMEM] = &mmfab_slv_mm_imem,
+};
+
+static const struct msm8660_icc_desc msm8660_mmfab = {
+	.nodes = msm8660_mmfab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_mmfab_nodes),
+	.bus_clks = msm8660_mmfab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_mmfab_clocks),
+	.rpm_resource = QCOM_RPM_MM_FABRIC_ARB,
+	.nmasters = 14,
+	.nslaves = 4,
+	.ntieredslaves = 3,
+	.default_tiered_slave = 2,	/* APPSS gateway */
+	.rpm_buf_size = 23,
+	.bus_width = 16,		/* 128-bit Multimedia fabric datapath */
+};
+
+/*
+ * =========================================================================
+ * Daytona Fabric (DFAB) nodes - peripheral bus for SDCC and ADM DMA
+ *
+ * DFAB connects slower peripherals to SFAB via the DFAB_TO_SFAB gateway.
+ * SDCC controllers (eMMC, SD card) connect here.
+ *
+ * No RPM ARB for DFAB - it's a simple peripheral bus with clock-only control.
+ *
+ * USB HS is included as a DFAB voter for compatibility with the legacy
+ * webOS kernel clock voting mechanism.
+ * =========================================================================
+ */
+DEFINE_QNODE(dfab_mas_sdc1, MSM8660_DFAB_MAS_SDC1, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc2, MSM8660_DFAB_MAS_SDC2, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc3, MSM8660_DFAB_MAS_SDC3, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc4, MSM8660_DFAB_MAS_SDC4, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc5, MSM8660_DFAB_MAS_SDC5, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_adm0_master, MSM8660_DFAB_MAS_ADM0_MASTER, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_adm1_master, MSM8660_DFAB_MAS_ADM1_MASTER, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_to_sfab, MSM8660_DFAB_TO_SFAB, 8, -1, -1, 0,
+	     MSM8660_SFAB_TO_DFAB);
+DEFINE_QNODE(dfab_slv_sdc1, MSM8660_DFAB_SLV_SDC1, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc2, MSM8660_DFAB_SLV_SDC2, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc3, MSM8660_DFAB_SLV_SDC3, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc4, MSM8660_DFAB_SLV_SDC4, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc5, MSM8660_DFAB_SLV_SDC5, 8, -1, -1, 0);
+/* USB HS DFAB voter - keeps DFAB clock stable during USB activity */
+DEFINE_QNODE(dfab_mas_usb_hs, MSM8660_DFAB_MAS_USB_HS, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+/* DSPS DFAB voter - keeps DFAB clock stable during sensor activity */
+DEFINE_QNODE(dfab_mas_dsps, MSM8660_DFAB_MAS_DSPS, 8, -1, -1, 0,
+	     MSM8660_DFAB_TO_SFAB);
+
+static struct msm8660_icc_node * const msm8660_dfab_nodes[] = {
+	[DFAB_MAS_SDC1] = &dfab_mas_sdc1,
+	[DFAB_MAS_SDC2] = &dfab_mas_sdc2,
+	[DFAB_MAS_SDC3] = &dfab_mas_sdc3,
+	[DFAB_MAS_SDC4] = &dfab_mas_sdc4,
+	[DFAB_MAS_SDC5] = &dfab_mas_sdc5,
+	[DFAB_MAS_ADM0_MASTER] = &dfab_mas_adm0_master,
+	[DFAB_MAS_ADM1_MASTER] = &dfab_mas_adm1_master,
+	[DFAB_TO_SFAB] = &dfab_to_sfab,
+	[DFAB_SLV_SDC1] = &dfab_slv_sdc1,
+	[DFAB_SLV_SDC2] = &dfab_slv_sdc2,
+	[DFAB_SLV_SDC3] = &dfab_slv_sdc3,
+	[DFAB_SLV_SDC4] = &dfab_slv_sdc4,
+	[DFAB_SLV_SDC5] = &dfab_slv_sdc5,
+	[DFAB_MAS_USB_HS] = &dfab_mas_usb_hs,
+	[DFAB_MAS_DSPS] = &dfab_mas_dsps,
+};
+
+static const struct msm8660_icc_desc msm8660_dfab = {
+	.nodes = msm8660_dfab_nodes,
+	.num_nodes = ARRAY_SIZE(msm8660_dfab_nodes),
+	.bus_clks = msm8660_dfab_clocks,
+	.num_clks = ARRAY_SIZE(msm8660_dfab_clocks),
+	.rpm_resource = -1,		/* No RPM ARB for DFAB */
+	.bus_width = 8,			/* 64-bit Daytona fabric datapath */
+};
+
+/*
+ * Pack bwsum[] and arb[] arrays into the u32 RPM buffer.
+ *
+ * Two u16 values are packed per u32 word: lower 16 bits first, upper 16 next.
+ * Layout: [bwsum pairs] then [arb pairs], handling odd boundaries.
+ *
+ * This matches the webOS msm_bus_fabric_rpm_commit() packing algorithm.
+ */
+static void msm8660_pack_rpm_data(const u16 *bwsum, int nslaves,
+				  const u16 *arb, int arb_size,
+				  u32 *buf)
+{
+	int i, index = 0;
+
+	/* Pack bwsum pairs */
+	for (i = 0; i + 1 < nslaves; i += 2) {
+		buf[index] = ((u32)bwsum[i + 1] << 16) | bwsum[i];
+		index++;
+	}
+
+	/* Handle boundary between bwsum and arb for odd nslaves */
+	if (nslaves & 1) {
+		buf[index] = ((u32)arb[0] << 16) | bwsum[i];
+		index++;
+		i = 1;	/* Start arb from index 1 (index 0 already packed) */
+	} else {
+		i = 0;
+	}
+
+	/* Pack arb pairs */
+	for (; i + 1 < arb_size; i += 2) {
+		buf[index] = ((u32)arb[i + 1] << 16) | arb[i];
+		index++;
+	}
+
+	/* Handle odd arb entry at end */
+	if (i < arb_size) {
+		buf[index] = arb[i];
+		index++;
+	}
+}
+
+/*
+ * Send fabric arbitration data to RPM.
+ *
+ * Iterates over all ICC nodes in the provider, builds bwsum/arb arrays
+ * from their aggregated bandwidth, and sends the packed data to RPM.
+ */
+static void msm8660_rpm_commit(struct msm8660_icc_provider *qp)
+{
+	const struct msm8660_icc_desc *desc = qp->desc;
+	struct icc_provider *provider = &qp->provider;
+	int nm = desc->nmasters;
+	int ns = desc->nslaves;
+	int nts = desc->ntieredslaves;
+	int arb_size = nm * nts;
+	int def_ts = desc->default_tiered_slave;
+	struct icc_node *n;
+	int ret;
+
+	memset(qp->bwsum, 0, ns * sizeof(u16));
+	memset(qp->arb, 0, arb_size * sizeof(u16));
+	memset(qp->rpm_buf, 0, desc->rpm_buf_size * sizeof(u32));
+
+	list_for_each_entry(n, &provider->nodes, node_list) {
+		struct msm8660_icc_node *qn = n->data;
+		u64 bw_bytes;
+		u16 bw_128k;
+
+		/* Use max of avg and peak bandwidth, convert to 128KB units */
+		bw_bytes = max(icc_units_to_bps(n->avg_bw),
+			       icc_units_to_bps(n->peak_bw));
+		bw_128k = (u16)min_t(u64, bw_bytes >> 17, ARB_BWMASK);
+
+		/* Set arb entry for masters at their default tiered slave */
+		if (qn->mas_port >= 0 && qn->mas_port < nm && def_ts > 0) {
+			int idx = (def_ts - 1) * nm + qn->mas_port;
+			u8 tier;
+
+			if (idx < arb_size) {
+				tier = bw_128k ? qn->mas_tier : ARB_TIER2;
+				qp->arb[idx] = (tier == ARB_TIER1 ? ARB_TIERMASK : 0)
+					       | (bw_128k & ARB_BWMASK);
+			}
+		}
+
+		/* Set bwsum for slaves */
+		if (qn->slv_port >= 0 && qn->slv_port < ns)
+			qp->bwsum[qn->slv_port] = bw_128k;
+	}
+
+	msm8660_pack_rpm_data(qp->bwsum, ns, qp->arb, arb_size, qp->rpm_buf);
+
+	ret = qcom_rpm_write(qp->rpm, QCOM_RPM_ACTIVE_STATE,
+			     desc->rpm_resource, qp->rpm_buf,
+			     desc->rpm_buf_size);
+	if (ret)
+		dev_err_ratelimited(provider->dev,
+				    "RPM fabric ARB write failed: %d\n", ret);
+}
+
+static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+	struct msm8660_icc_node *src_qn;
+	struct msm8660_icc_provider *qp;
+	u64 sum_bw, max_peak_bw, rate;
+	u32 agg_avg = 0, agg_peak = 0;
+	struct icc_provider *provider;
+	struct icc_node *n;
+	int ret, i;
+
+	src_qn = src->data;
+	provider = src->provider;
+	qp = to_msm8660_icc_provider(provider);
+
+	list_for_each_entry(n, &provider->nodes, node_list)
+		provider->aggregate(n, 0, n->avg_bw, n->peak_bw,
+				    &agg_avg, &agg_peak);
+
+	sum_bw = icc_units_to_bps(agg_avg);
+	max_peak_bw = icc_units_to_bps(agg_peak);
+
+	/*
+	 * Divide by the *fabric* bus width, not src_qn->buswidth: every
+	 * master on a given fabric shares the same hardware clock, so the
+	 * required clock rate is a single function of total bandwidth and
+	 * the fabric's bus width. Picking the bus width of whichever node
+	 * happened to trigger this update would make the rate oscillate
+	 * depending on which master called icc_set_bw() last.
+	 */
+	rate = max(sum_bw, max_peak_bw);
+	do_div(rate, qp->desc->bus_width);
+	/* Apply minimum floor to prevent bus starvation */
+	rate = max_t(u64, rate, MSM8660_FABRIC_MIN_RATE);
+	rate = min_t(u32, rate, INT_MAX);
+
+	if (qp->rate != rate) {
+		for (i = 0; i < qp->num_clks; i++) {
+			ret = clk_set_rate(qp->bus_clks[i].clk, rate);
+			if (ret) {
+				dev_err(provider->dev,
+					"%s clk_set_rate error: %d\n",
+					qp->bus_clks[i].id, ret);
+				ret = 0;
+			}
+		}
+		qp->rate = rate;
+	}
+
+	/* Send RPM fabric arbitration if available */
+	if (qp->rpm && qp->desc->rpm_resource >= 0)
+		msm8660_rpm_commit(qp);
+
+	return 0;
+}
+
+static int msm8660_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
+{
+	*avg = 0;
+	*peak = 0;
+	return 0;
+}
+
+/*
+ * Look up the RPM that owns fabric arbitration writes.
+ *
+ * Returns NULL if the DT does not have a "qcom,rpm" phandle (in which
+ * case the caller silently drops RPM ARB and runs the fabric purely
+ * via clk_set_rate).
+ *
+ * Returns ERR_PTR(-EPROBE_DEFER) if the RPM device exists in DT but
+ * its driver has not finished probing yet, or if device_link_add()
+ * fails. The caller is expected to propagate this so the interconnect
+ * driver gets retried once the RPM is ready.
+ *
+ * On success returns the qcom_rpm handle and pins the RPM device
+ * lifetime to ours via a consumer-supplier device link, so the
+ * devres-allocated qcom_rpm cannot be freed while we still hold a
+ * pointer to it.
+ */
+static struct qcom_rpm *msm8660_get_rpm(struct device *dev)
+{
+	struct device_node *rpm_np;
+	struct platform_device *rpm_pdev;
+	struct device_link *link;
+	struct qcom_rpm *rpm;
+
+	rpm_np = of_parse_phandle(dev->of_node, "qcom,rpm", 0);
+	if (!rpm_np) {
+		dev_dbg(dev, "no qcom,rpm phandle, RPM ARB disabled\n");
+		return NULL;
+	}
+
+	rpm_pdev = of_find_device_by_node(rpm_np);
+	of_node_put(rpm_np);
+	if (!rpm_pdev) {
+		dev_dbg(dev, "RPM device not found yet, deferring probe\n");
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	rpm = dev_get_drvdata(&rpm_pdev->dev);
+	if (!rpm) {
+		put_device(&rpm_pdev->dev);
+		dev_dbg(dev, "RPM not ready, deferring probe\n");
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	/*
+	 * Keep the RPM device alive for as long as we are bound; without
+	 * a device link, the devres-managed qcom_rpm could be released
+	 * out from under us if the RPM driver were unbound at runtime,
+	 * leaving a dangling pointer in qp->rpm.
+	 */
+	link = device_link_add(dev, &rpm_pdev->dev,
+			       DL_FLAG_AUTOREMOVE_CONSUMER);
+	put_device(&rpm_pdev->dev);
+	if (!link) {
+		dev_warn(dev, "failed to add device link to RPM, deferring\n");
+		return ERR_PTR(-EPROBE_DEFER);
+	}
+
+	return rpm;
+}
+
+static int msm8660_icc_probe(struct platform_device *pdev)
+{
+	const struct msm8660_icc_desc *desc;
+	struct msm8660_icc_node * const *qnodes;
+	struct msm8660_icc_provider *qp;
+	struct device *dev = &pdev->dev;
+	struct icc_onecell_data *data;
+	struct icc_provider *provider;
+	struct icc_node *node;
+	size_t num_nodes, i;
+	int ret;
+
+	desc = of_device_get_match_data(dev);
+	if (!desc)
+		return -EINVAL;
+
+	qnodes = desc->nodes;
+	num_nodes = desc->num_nodes;
+
+	qp = devm_kzalloc(dev, sizeof(*qp), GFP_KERNEL);
+	if (!qp)
+		return -ENOMEM;
+
+	data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes),
+			    GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	data->num_nodes = num_nodes;
+
+	qp->bus_clks = devm_kmemdup(dev, desc->bus_clks,
+				    desc->num_clks * sizeof(*desc->bus_clks),
+				    GFP_KERNEL);
+	if (!qp->bus_clks)
+		return -ENOMEM;
+
+	qp->num_clks = desc->num_clks;
+
+	/*
+	 * MSM8660 fabric clocks are managed by RPM firmware and may not be
+	 * available in mainline Linux yet. Once the clock provider exists,
+	 * we want to honour it; until then we run without per-fabric clock
+	 * scaling. The crucial part is that -EPROBE_DEFER means "the
+	 * provider exists but hasn't probed yet" and MUST be propagated so
+	 * we get retried; only other errors (genuine -ENOENT, etc.) get
+	 * downgraded to "no clocks, continue".
+	 */
+	ret = devm_clk_bulk_get_optional(dev, qp->num_clks, qp->bus_clks);
+	if (ret == -EPROBE_DEFER)
+		return ret;
+	if (ret) {
+		dev_warn(dev, "Failed to get bus clocks: %d (continuing without clock scaling)\n",
+			 ret);
+		qp->num_clks = 0;
+	}
+
+	if (qp->num_clks) {
+		ret = clk_bulk_prepare_enable(qp->num_clks, qp->bus_clks);
+		if (ret) {
+			dev_warn(dev, "Failed to enable bus clocks: %d\n", ret);
+			qp->num_clks = 0;
+		}
+	}
+
+	/* Set up RPM fabric arbitration */
+	qp->desc = desc;
+	if (desc->rpm_resource >= 0) {
+		qp->rpm = msm8660_get_rpm(dev);
+		if (IS_ERR(qp->rpm))
+			return PTR_ERR(qp->rpm);
+		if (qp->rpm) {
+			int arb_size = desc->nmasters * desc->ntieredslaves;
+
+			qp->bwsum = devm_kcalloc(dev, desc->nslaves,
+						 sizeof(u16), GFP_KERNEL);
+			qp->arb = devm_kcalloc(dev, arb_size + 1,
+					       sizeof(u16), GFP_KERNEL);
+			qp->rpm_buf = devm_kcalloc(dev, desc->rpm_buf_size,
+						   sizeof(u32), GFP_KERNEL);
+			if (!qp->bwsum || !qp->arb || !qp->rpm_buf) {
+				dev_warn(dev, "RPM buffer alloc failed, ARB disabled\n");
+				qp->rpm = NULL;
+			} else {
+				int rc;
+
+				dev_info(dev, "RPM fabric ARB enabled (%d masters, %d slaves, %d tiered)\n",
+					 desc->nmasters, desc->nslaves,
+					 desc->ntieredslaves);
+
+				/*
+				 * One-shot sleep-context vote of zero bandwidth.
+				 * Without an explicit SLEEP_STATE write, RPM has no
+				 * fabric bandwidth target for deep-sleep and may
+				 * keep the active vote applied indefinitely,
+				 * preventing DDR from dropping its rate when CPUs
+				 * power-collapse. The buffer is devm_kcalloc'd so
+				 * it is all-zero at this point — written before
+				 * any consumer can drive an active vote that would
+				 * dirty it.
+				 *
+				 * msm8660_rpm_commit() writes ACTIVE_STATE only;
+				 * SLEEP_STATE remains zero for the provider's
+				 * lifetime, so this vote does not need refreshing.
+				 */
+				rc = qcom_rpm_write(qp->rpm,
+						    QCOM_RPM_SLEEP_STATE,
+						    desc->rpm_resource,
+						    qp->rpm_buf,
+						    desc->rpm_buf_size);
+				if (rc)
+					dev_warn(dev, "RPM fabric sleep vote failed: %d\n",
+						 rc);
+			}
+		}
+	}
+
+	provider = &qp->provider;
+	provider->dev = dev;
+	provider->set = msm8660_icc_set;
+	provider->aggregate = icc_std_aggregate;
+	provider->xlate = of_icc_xlate_onecell;
+	provider->data = data;
+	provider->get_bw = msm8660_get_bw;
+
+	icc_provider_init(provider);
+
+	for (i = 0; i < num_nodes; i++) {
+		size_t j;
+
+		if (!qnodes[i])
+			continue;
+
+		node = icc_node_create(qnodes[i]->id);
+		if (IS_ERR(node)) {
+			ret = PTR_ERR(node);
+			goto err_remove_nodes;
+		}
+
+		node->name = qnodes[i]->name;
+		node->data = qnodes[i];
+		icc_node_add(node, provider);
+
+		dev_dbg(dev, "registered node %s\n", node->name);
+
+		/* populate links */
+		for (j = 0; j < qnodes[i]->num_links; j++)
+			icc_link_create(node, qnodes[i]->links[j]);
+
+		data->nodes[i] = node;
+	}
+
+	ret = icc_provider_register(provider);
+	if (ret)
+		goto err_remove_nodes;
+
+	platform_set_drvdata(pdev, qp);
+
+	dev_info(dev, "MSM8660 interconnect provider registered\n");
+
+	return 0;
+
+err_remove_nodes:
+	icc_nodes_remove(provider);
+	clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+
+	return ret;
+}
+
+static void msm8660_icc_remove(struct platform_device *pdev)
+{
+	struct msm8660_icc_provider *qp = platform_get_drvdata(pdev);
+
+	icc_provider_deregister(&qp->provider);
+	icc_nodes_remove(&qp->provider);
+	if (qp->num_clks)
+		clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+}
+
+static const struct of_device_id msm8660_noc_of_match[] = {
+	{ .compatible = "qcom,msm8660-apps-fabric", .data = &msm8660_afab },
+	{ .compatible = "qcom,msm8660-system-fabric", .data = &msm8660_sfab },
+	{ .compatible = "qcom,msm8660-mmss-fabric", .data = &msm8660_mmfab },
+	{ .compatible = "qcom,msm8660-daytona-fabric", .data = &msm8660_dfab },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, msm8660_noc_of_match);
+
+static struct platform_driver msm8660_noc_driver = {
+	.probe = msm8660_icc_probe,
+	.remove = msm8660_icc_remove,
+	.driver = {
+		.name = "qnoc-msm8660",
+		.of_match_table = msm8660_noc_of_match,
+		.sync_state = icc_sync_state,
+	},
+};
+/*
+ * Register the NOC provider at core_initcall, matching the mainline pattern
+ * used by newer Qualcomm SoCs (sm8450, glymur, qdu1000, sc8280xp, sm8750).
+ *
+ * Why not module_platform_driver (device_initcall)?  drivers/Makefile lists
+ * drivers/interconnect/ at position 189, *after* every ICC-consumer subdir
+ * (clk/ @40, soc/ @46, gpu/ @68, base/mfd/ @76, spmi/ @89, usb/ @106,
+ * i2c/ @116, mmc/ @133, remoteproc/ @158).  Within a single initcall level
+ * execution order = link order, so a device_initcall registration here runs
+ * *after* every consumer has already tried to probe.  Mainline relies on
+ * deferred-probe retry to recover from that, but in this tree some consumer
+ * (apcs-msm8660 + cpufreq cascade suspected) fails to recover within
+ * deferred_probe_timeout=5 and boot dies at the Tux splash with no rootfs.
+ * Empirically confirmed 2026-05-29 with module_platform_driver (commits
+ * 99275d8a8ae9 + ca35c591854c, reverted).
+ *
+ * icc_provider_register does not require icc_init to have run first --
+ * the framework's locks are statically DEFINE_MUTEX'd -- so registering
+ * the provider at core_initcall (before icc_init at subsys_initcall) is
+ * safe, same as mainline sm8450 etc.
+ */
+static int __init msm8660_noc_driver_init(void)
+{
+	return platform_driver_register(&msm8660_noc_driver);
+}
+core_initcall(msm8660_noc_driver_init);
+
+static void __exit msm8660_noc_driver_exit(void)
+{
+	platform_driver_unregister(&msm8660_noc_driver);
+}
+module_exit(msm8660_noc_driver_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 interconnect driver");
+MODULE_LICENSE("GPL v2");
-- 
2.43.0


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

* [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver
  2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
                   ` (4 preceding siblings ...)
  2026-05-30 14:00 ` [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
@ 2026-05-31  4:09 ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
                     ` (5 more replies)
  5 siblings, 6 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, Lee Jones, linux-arm-msm, linux-kernel,
	linux-pm, Lukasz Luba, Rafael J. Wysocki, Rob Herring,
	Satya Priya, Thara Gopinath, van Hazendonk, Zhang Rui

Hi all,

Self-review (with Sashiko AI assist) caught issues in v1 before
maintainer review reached them; re-rolling promptly. v1:

  https://lore.kernel.org/linux-arm-msm/cover.1780148149.git.github.com@herrie.org/

v2 changes:

  - NEW patch 1/3: extend the qcom,pm8xxx parent schema with a
    `temp-alarm@[0-9a-f]+$` patternProperty so the new sub-node
    validates as a recognised child of the PMIC. Without this, any
    board DT instantiating the temp-alarm sub-node fails
    dt_binding_check (CI bot caught this on v1).

  - patch 2/3: thermal binding YAML rewritten:
      * add `allOf: $ref: /schemas/thermal/thermal-sensor.yaml#`;
      * rewrite the example so the parent qcom,pm8901 node itself
        satisfies its own schema (reg, address-cells, interrupts,
        interrupt-controller) - addresses the CI bot's `'interrupts'
        is a required property` complaint on the embedded pmic node;
      * reword the commit message; v1 incorrectly said the binding
        describes the "GIC interrupt" and "parent PMIC reference"
        (the interrupts are actually PMIC-internal, and the parent
        relationship is the standard DT parent-child hierarchy).

  - patch 3/3: driver fixes:
      * defer the SW-override switch (which disables PMIC HW
        auto-shutdown) to the very end of probe and install a devm
        action that restores HW auto-shutdown on unbind, so the part
        is never left without any thermal protection if an earlier
        probe step fails;
      * fix the first-read temperature comment: the formula computes
        the lower bound of the current stage, not the midpoint;
      * snapshot chip->stage/thresh/temp under chip->lock before
        printing the boot banner so the values are consistent now
        that the ISR is live;
      * drop the explicit ->remove(), the new devm restore action
        replaces it.

dt_binding_check passes on both the parent qcom,pm8xxx and the new
qcom,pm8901-temp-alarm. Driver passes checkpatch with zero warnings
or errors.

Thanks,
Herman

Herman van Hazendonk (3):
  dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode
  dt-bindings: thermal: qcom: add pm8901-temp-alarm
  thermal: qcom: add PM8901 PMIC temperature-alarm driver

 .../devicetree/bindings/mfd/qcom-pm8xxx.yaml  |   4 +
 .../thermal/qcom,pm8901-temp-alarm.yaml       |  90 ++++
 drivers/thermal/qcom/Kconfig                  |  12 +
 drivers/thermal/qcom/Makefile                 |   1 +
 drivers/thermal/qcom/qcom-pm8901-tm.c         | 408 ++++++++++++++++++
 5 files changed, 515 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
 create mode 100644 drivers/thermal/qcom/qcom-pm8901-tm.c

-- 
2.43.0


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

* [PATCH v2 1/3] dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
                     ` (2 preceding siblings ...)
  2026-05-31  4:09   ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 2/3] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, Lee Jones, linux-arm-msm, linux-kernel,
	linux-pm, Lukasz Luba, Rafael J. Wysocki, Rob Herring,
	Satya Priya, Thara Gopinath, van Hazendonk, Zhang Rui

The qcom,pm8xxx parent schema closes its child set via
`additionalProperties: false` and an explicit list of `patternProperties`.
PM8901 (and prospectively other parts in the family) exposes an
over-temperature alarm block as an SSBI sub-node; without a matching
pattern here, any board DT that instantiates `temp-alarm@<offset>`
under a PM8xxx parent fails dtbs_check.

Add the `temp-alarm@[0-9a-f]+$` pattern, referencing the new
qcom,pm8901-temp-alarm schema, so the temperature-alarm sub-node
validates as a recognised child of the PMIC.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 Documentation/devicetree/bindings/mfd/qcom-pm8xxx.yaml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/devicetree/bindings/mfd/qcom-pm8xxx.yaml b/Documentation/devicetree/bindings/mfd/qcom-pm8xxx.yaml
index 63e18d6a9c21..dde290569b03 100644
--- a/Documentation/devicetree/bindings/mfd/qcom-pm8xxx.yaml
+++ b/Documentation/devicetree/bindings/mfd/qcom-pm8xxx.yaml
@@ -68,6 +68,10 @@ patternProperties:
     type: object
     $ref: /schemas/rtc/qcom-pm8xxx-rtc.yaml#
 
+  "temp-alarm@[0-9a-f]+$":
+    type: object
+    $ref: /schemas/thermal/qcom,pm8901-temp-alarm.yaml#
+
   "vibrator@[0-9a-f]+$":
     type: object
     $ref: /schemas/input/qcom,pm8xxx-vib.yaml#
-- 
2.43.0


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

* [PATCH v2 2/3] dt-bindings: thermal: qcom: add pm8901-temp-alarm
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
                     ` (3 preceding siblings ...)
  2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  2026-05-31  4:09   ` [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, Lee Jones, linux-arm-msm, linux-kernel,
	linux-pm, Lukasz Luba, Rafael J. Wysocki, Rob Herring,
	Satya Priya, Thara Gopinath, van Hazendonk, Zhang Rui

Add the binding for the temperature-alarm block inside the Qualcomm
PM8901 PMIC (companion to the PM8058 on MSM8x60). The block has four
selectable thresholds and three escalating stages; the driver maps
these to representative millicelsius readings exposed to the
thermal-of framework, and a board DT can wire stage 3 as a critical
trip so the kernel issues orderly_poweroff() when the part overheats.

The binding describes the SSBI sub-node address (CTRL register
offset) and the two PMIC-internal interrupts the alarm raises:

  - TEMP_ALARM    (PM8901 IRQ block 6 bit 4 == hwirq 52),
                  asserted on every stage transition;
  - TEMP_HI_ALARM (PM8901 IRQ block 6 bit 5 == hwirq 53),
                  asserted when the high-temperature stage is reached.

The interrupts are sourced from the parent qcom,pm8901 PMIC's own
interrupt-controller (not the SoC GIC); the node references the core
/schemas/thermal/thermal-sensor.yaml so that the standard
#thermal-sensor-cells handling and other shared thermal-sensor
constraints are inherited automatically.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 .../thermal/qcom,pm8901-temp-alarm.yaml       | 90 +++++++++++++++++++
 1 file changed, 90 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml

diff --git a/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml b/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
new file mode 100644
index 000000000000..5d9eaeab8326
--- /dev/null
+++ b/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/thermal/qcom,pm8901-temp-alarm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm PM8901 PMIC Temperature Alarm
+
+maintainers:
+  - Herman van Hazendonk <github.com@herrie.org>
+
+description: |
+  PM8901 is a secondary PMIC paired with PM8058 on MSM8x60 family
+  (MSM8260/MSM8660/APQ8060) platforms. It exposes an over-temperature
+  alarm block at SSBI offset 0x23 (CTRL) with four selectable
+  thresholds and three escalating stages.
+
+  Unlike PM8058, there is no raw die-temperature ADC channel - the
+  driver decodes the stage + threshold pair into a representative
+  millicelsius value reported via the thermal-of framework.
+
+  Two PMIC-internal interrupts are exposed:
+
+    - TEMP_ALARM    (PM8901 IRQ block 6 bit 4 == hwirq 52): asserted
+                    on every stage transition;
+    - TEMP_HI_ALARM (PM8901 IRQ block 6 bit 5 == hwirq 53): asserted
+                    when the part enters the high-temperature stage.
+
+  Both line up on the parent qcom,pm8901 interrupt-controller.
+
+allOf:
+  - $ref: /schemas/thermal/thermal-sensor.yaml#
+
+properties:
+  compatible:
+    const: qcom,pm8901-temp-alarm
+
+  reg:
+    description: SSBI offset of the temp-alarm CTRL register.
+    maxItems: 1
+
+  interrupts:
+    items:
+      - description: Stage-transition alarm interrupt (TEMP_ALARM).
+      - description: Hi-temperature alarm interrupt (TEMP_HI_ALARM).
+
+  interrupt-names:
+    items:
+      - const: alarm
+      - const: hi-alarm
+
+  "#thermal-sensor-cells":
+    const: 0
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    ssbi {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      pmic@0 {
+        compatible = "qcom,pm8901";
+        reg = <0>;
+        #address-cells = <1>;
+        #size-cells = <0>;
+        #interrupt-cells = <2>;
+        interrupt-controller;
+        interrupt-parent = <&tlmm>;
+        interrupts = <31 IRQ_TYPE_EDGE_RISING>;
+
+        temp-alarm@23 {
+          compatible = "qcom,pm8901-temp-alarm";
+          reg = <0x23>;
+          interrupts = <52 IRQ_TYPE_EDGE_RISING>,
+                       <53 IRQ_TYPE_EDGE_RISING>;
+          interrupt-names = "alarm", "hi-alarm";
+          #thermal-sensor-cells = <0>;
+        };
+      };
+    };
-- 
2.43.0


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

* [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver
  2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
                     ` (4 preceding siblings ...)
  2026-05-31  4:09   ` [PATCH v2 2/3] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
@ 2026-05-31  4:09   ` Herman van Hazendonk
  5 siblings, 0 replies; 14+ messages in thread
From: Herman van Hazendonk @ 2026-05-31  4:09 UTC (permalink / raw)
  To: Amit Kucheria, Conor Dooley, Daniel Lezcano, devicetree,
	Krzysztof Kozlowski, Lee Jones, linux-arm-msm, linux-kernel,
	linux-pm, Lukasz Luba, Rafael J. Wysocki, Rob Herring,
	Satya Priya, Thara Gopinath, van Hazendonk, Zhang Rui

Add a thermal-of sensor driver for the temperature-alarm block inside
the Qualcomm PM8901 PMIC. PM8901 is a secondary PMIC paired with
PM8058 on the MSM8x60 family (MSM8260/MSM8660/APQ8060). It exposes
an over-temperature alarm at SSBI offset 0x23/0x24 with three
escalating stages (105/125/145 C); the driver decodes the stage +
threshold pair into a millicelsius reading and registers two PMIC-
internal interrupts (TEMP_ALARM at block 6 bit 4, TEMP_HI_ALARM at
block 6 bit 5).

Used by board thermal-zones for the orderly_poweroff path on the HP
TouchPad.

Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
 drivers/thermal/qcom/Kconfig          |  12 +
 drivers/thermal/qcom/Makefile         |   1 +
 drivers/thermal/qcom/qcom-pm8901-tm.c | 408 ++++++++++++++++++++++++++
 3 files changed, 421 insertions(+)
 create mode 100644 drivers/thermal/qcom/qcom-pm8901-tm.c

diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig
index a6bb01082ec6..af099032f1e6 100644
--- a/drivers/thermal/qcom/Kconfig
+++ b/drivers/thermal/qcom/Kconfig
@@ -32,6 +32,18 @@ config QCOM_SPMI_TEMP_ALARM
 	  real time die temperature if an ADC is present or an estimate of the
 	  temperature based upon the over temperature stage value.
 
+config QCOM_PM8901_TEMP_ALARM
+	tristate "Qualcomm PM8901 PMIC Temperature Alarm"
+	depends on MFD_PM8XXX || COMPILE_TEST
+	depends on THERMAL_OF
+	help
+	  This enables the thermal driver for the PM8901 PMIC over-temperature
+	  alarm block. PM8901 exposes a stage-based alarm (no raw ADC) with
+	  four selectable thresholds and three escalating stages. The driver
+	  registers a thermal-of sensor so a board device tree can declare
+	  trip points and a critical-trip action (orderly_poweroff). Used on
+	  HP TouchPad (APQ8060) where PM8901 supplies the secondary PMIC die.
+
 config QCOM_LMH
 	tristate "Qualcomm Limits Management Hardware"
 	depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile
index 0fa2512042e7..90dc05151e33 100644
--- a/drivers/thermal/qcom/Makefile
+++ b/drivers/thermal/qcom/Makefile
@@ -5,4 +5,5 @@ qcom_tsens-y			+= tsens.o tsens-v2.o tsens-v1.o tsens-v0_1.o \
 				   tsens-8960.o
 obj-$(CONFIG_QCOM_SPMI_ADC_TM5)	+= qcom-spmi-adc-tm5.o
 obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM)	+= qcom-spmi-temp-alarm.o
+obj-$(CONFIG_QCOM_PM8901_TEMP_ALARM)	+= qcom-pm8901-tm.o
 obj-$(CONFIG_QCOM_LMH)		+= lmh.o
diff --git a/drivers/thermal/qcom/qcom-pm8901-tm.c b/drivers/thermal/qcom/qcom-pm8901-tm.c
new file mode 100644
index 000000000000..b159e9917c2c
--- /dev/null
+++ b/drivers/thermal/qcom/qcom-pm8901-tm.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Qualcomm PM8901 PMIC Thermal-Alarm Driver
+ *
+ * Mainline port of the legacy 2.6.35-palm drivers/thermal/pmic8901-tm.c.
+ * PM8901 exposes a stage-based over-temperature alarm (no raw ADC) with
+ * four selectable thresholds and three escalating stages. This driver
+ * mirrors the legacy programming exactly (threshold-set 0, software
+ * override enabled, PWM gating at 8 Hz) and registers a thermal-of
+ * sensor so a board DT can declare trip points and a critical action.
+ *
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/thermal.h>
+
+/* SSBI registers (offsets from the per-instance reg base) */
+#define PM8901_TM_REG_CTRL		0x00	/* CTRL/STATUS  (base + 0) */
+#define PM8901_TM_REG_PWM		0x01	/* PWM gating  (base + 1) */
+
+/* CTRL register fields */
+#define CTRL_ST3_SD			BIT(7)
+#define CTRL_ST2_SD			BIT(6)
+#define CTRL_STATUS_MASK		GENMASK(5, 4)
+#define CTRL_THRESH_MASK		GENMASK(3, 2)
+#define CTRL_OVRD_ST3			BIT(1)
+#define CTRL_OVRD_ST2			BIT(0)
+#define CTRL_OVRD_MASK			GENMASK(1, 0)
+
+/* PWM register fields */
+#define PWM_EN				BIT(7)
+#define PWM_PER_PRE_MASK		GENMASK(5, 3)
+#define PWM_PER_DIV_MASK		GENMASK(2, 0)
+
+/* Temperature math (from legacy pmic8901-tm.c) */
+#define PM8901_TEMP_STAGE_STEP		20000	/* 20 deg C between stages */
+#define PM8901_TEMP_STAGE_HYSTERESIS	2000	/*  2 deg C transition guard */
+#define PM8901_TEMP_THRESH_MIN		105000	/* threshold 0 base = 105 C */
+#define PM8901_TEMP_THRESH_STEP		5000	/*  5 deg C per threshold step */
+
+/*
+ * PM8901 has no real die ADC; when stage == 0 ("below threshold") we
+ * report a plausible idle estimate matching the legacy DEFAULT_NO_ADC_TEMP.
+ */
+#define PM8901_TEMP_NO_ALARM		37000
+
+struct pm8901_tm_chip {
+	struct device			*dev;
+	struct regmap			*map;
+	struct thermal_zone_device	*tz_dev;
+	struct mutex			lock;
+	unsigned int			base;	/* SSBI offset, from DT reg */
+	unsigned int			stage;
+	unsigned int			thresh;
+	int				temp;
+	bool				initialised;
+};
+
+static int pm8901_tm_read_ctrl(struct pm8901_tm_chip *chip, u8 *val)
+{
+	unsigned int v;
+	int ret;
+
+	ret = regmap_read(chip->map, chip->base + PM8901_TM_REG_CTRL, &v);
+	if (!ret)
+		*val = v;
+	return ret;
+}
+
+static int pm8901_tm_write_ctrl(struct pm8901_tm_chip *chip, u8 val)
+{
+	return regmap_write(chip->map, chip->base + PM8901_TM_REG_CTRL, val);
+}
+
+static int pm8901_tm_write_pwm(struct pm8901_tm_chip *chip, u8 val)
+{
+	return regmap_write(chip->map, chip->base + PM8901_TM_REG_PWM, val);
+}
+
+/*
+ * Decode the (stage, threshold) pair into a single millicelsius value.
+ * Logic matches the legacy pmic8901-tm.c hysteresis selection:
+ *  - on a rising stage transition, use the lower bound of the new stage
+ *    plus +HYSTERESIS so we don't bounce
+ *  - on a falling stage transition, use the upper bound of the new stage
+ *    minus -HYSTERESIS
+ *  - on the first read after probe (initialised == false), report the
+ *    lower bound of the current stage (the most conservative estimate
+ *    given that the hardware only tells us "we crossed this stage's
+ *    threshold"), or PM8901_TEMP_NO_ALARM when stage == 0.
+ */
+static int pm8901_tm_update_temp_locked(struct pm8901_tm_chip *chip)
+{
+	unsigned int new_stage;
+	u8 reg;
+	int ret;
+
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (ret)
+		return ret;
+
+	new_stage = FIELD_GET(CTRL_STATUS_MASK, reg);
+	chip->thresh = FIELD_GET(CTRL_THRESH_MASK, reg);
+
+	if (!chip->initialised) {
+		if (new_stage)
+			chip->temp = PM8901_TEMP_THRESH_MIN +
+				     chip->thresh * PM8901_TEMP_THRESH_STEP +
+				     (new_stage - 1) * PM8901_TEMP_STAGE_STEP;
+		else
+			chip->temp = PM8901_TEMP_NO_ALARM;
+		chip->initialised = true;
+	} else if (new_stage > chip->stage) {
+		chip->temp = PM8901_TEMP_THRESH_MIN +
+			     chip->thresh * PM8901_TEMP_THRESH_STEP +
+			     (new_stage - 1) * PM8901_TEMP_STAGE_STEP +
+			     PM8901_TEMP_STAGE_HYSTERESIS;
+	} else if (new_stage < chip->stage) {
+		chip->temp = PM8901_TEMP_THRESH_MIN +
+			     chip->thresh * PM8901_TEMP_THRESH_STEP +
+			     new_stage * PM8901_TEMP_STAGE_STEP -
+			     PM8901_TEMP_STAGE_HYSTERESIS;
+	}
+
+	chip->stage = new_stage;
+	return 0;
+}
+
+static int pm8901_tm_get_temp(struct thermal_zone_device *tz, int *temp)
+{
+	struct pm8901_tm_chip *chip = thermal_zone_device_priv(tz);
+	int ret;
+
+	if (!temp)
+		return -EINVAL;
+
+	mutex_lock(&chip->lock);
+	ret = pm8901_tm_update_temp_locked(chip);
+	if (!ret)
+		*temp = chip->temp;
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static const struct thermal_zone_device_ops pm8901_tm_zone_ops = {
+	.get_temp = pm8901_tm_get_temp,
+};
+
+/*
+ * Shared ISR for both TEMP_ALARM (stage-transition) and TEMP_HI_ALARM
+ * (hi-temp) interrupts. Updates the cached temperature, clears any
+ * latched ST2_SD / ST3_SD shutdown bits so the next stage transition
+ * can be observed, and pokes the thermal core which then re-reads
+ * temp and walks trips (a critical-trip cross triggers orderly_poweroff
+ * via the kernel's standard machinery).
+ */
+static irqreturn_t pm8901_tm_isr(int irq, void *data)
+{
+	struct pm8901_tm_chip *chip = data;
+	u8 reg;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = pm8901_tm_update_temp_locked(chip);
+	if (ret) {
+		mutex_unlock(&chip->lock);
+		return IRQ_HANDLED;
+	}
+
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (!ret && (reg & (CTRL_ST2_SD | CTRL_ST3_SD))) {
+		reg &= ~(CTRL_ST2_SD | CTRL_ST3_SD | CTRL_STATUS_MASK);
+		pm8901_tm_write_ctrl(chip, reg);
+	}
+
+	dev_dbg(chip->dev, "alarm irq=%d stage=%u thresh=%u temp=%d\n",
+		irq, chip->stage, chip->thresh, chip->temp);
+
+	mutex_unlock(&chip->lock);
+
+	thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Program PM8901 to the legacy default: threshold-set 0 (105 / 125 / 145 C),
+ * PWM at 8 Hz (legacy "cut down on unnecessary interrupts" rate), and
+ * prime the cached temperature. This intentionally does NOT yet flip the
+ * software-override bits; HW auto-shutdown is left enabled here so the
+ * PMIC keeps protecting the part if any later probe step fails. The
+ * SW-override switch happens in pm8901_tm_enable_sw_override() and is
+ * paired with a devm action that reverts the bits if the driver unbinds.
+ */
+static int pm8901_tm_init_hw(struct pm8901_tm_chip *chip)
+{
+	int ret;
+	u8 reg;
+
+	mutex_lock(&chip->lock);
+
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (ret)
+		goto out;
+
+	/* Clear status + threshold bits, leave OVRD bits as the HW found them. */
+	reg = reg & ~(CTRL_STATUS_MASK | CTRL_THRESH_MASK);
+	ret = pm8901_tm_write_ctrl(chip, reg);
+	if (ret)
+		goto out;
+
+	chip->thresh = 0;
+
+	/* PWM @ 8 Hz: PWM_EN | PRE=3 | DIV=3 — verbatim from legacy. */
+	reg = PWM_EN | FIELD_PREP(PWM_PER_PRE_MASK, 3) |
+	      FIELD_PREP(PWM_PER_DIV_MASK, 3);
+	ret = pm8901_tm_write_pwm(chip, reg);
+	if (ret)
+		goto out;
+
+	/* Prime the cached temperature from current hardware state. */
+	chip->initialised = false;
+	ret = pm8901_tm_update_temp_locked(chip);
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+/*
+ * Re-enable PM8901's hardware auto-cut on the way out, so the PMIC takes
+ * over thermal protection again once the kernel is no longer the
+ * shutdown agent. Best-effort: log on failure, do not propagate the
+ * error (there is nothing the unbind path can do about it).
+ */
+static void pm8901_tm_restore_hw_shutdown(void *data)
+{
+	struct pm8901_tm_chip *chip = data;
+	int ret;
+	u8 reg;
+
+	mutex_lock(&chip->lock);
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (!ret) {
+		reg &= ~CTRL_OVRD_MASK;
+		ret = pm8901_tm_write_ctrl(chip, reg);
+	}
+	mutex_unlock(&chip->lock);
+
+	if (ret)
+		dev_warn(chip->dev,
+			 "failed to restore PMIC HW auto-shutdown: %d\n", ret);
+}
+
+/*
+ * Hand thermal protection responsibility from the PMIC's hardware
+ * auto-cut to the kernel thermal core. This is the LAST step of probe
+ * so that, if any earlier step fails, the PMIC keeps protecting the
+ * part on its own. Once it succeeds we install a devm action that
+ * re-enables HW auto-cut if/when the driver is unbound.
+ */
+static int pm8901_tm_enable_sw_override(struct pm8901_tm_chip *chip)
+{
+	int ret;
+	u8 reg;
+
+	mutex_lock(&chip->lock);
+	ret = pm8901_tm_read_ctrl(chip, &reg);
+	if (!ret) {
+		reg = (reg & ~CTRL_OVRD_MASK) | CTRL_OVRD_ST3 | CTRL_OVRD_ST2;
+		ret = pm8901_tm_write_ctrl(chip, reg);
+	}
+	mutex_unlock(&chip->lock);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(chip->dev,
+					pm8901_tm_restore_hw_shutdown, chip);
+}
+
+static int pm8901_tm_probe(struct platform_device *pdev)
+{
+	struct pm8901_tm_chip *chip;
+	int ret, irq_alarm, irq_hi_alarm;
+	u32 res;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = &pdev->dev;
+	mutex_init(&chip->lock);
+
+	chip->map = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!chip->map)
+		return dev_err_probe(&pdev->dev, -ENXIO,
+				     "no regmap on PM8901 parent\n");
+
+	ret = of_property_read_u32(pdev->dev.of_node, "reg", &res);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "missing reg property\n");
+	chip->base = res;
+
+	irq_alarm = platform_get_irq_byname(pdev, "alarm");
+	if (irq_alarm < 0)
+		return irq_alarm;
+	irq_hi_alarm = platform_get_irq_byname(pdev, "hi-alarm");
+	if (irq_hi_alarm < 0)
+		return irq_hi_alarm;
+
+	ret = pm8901_tm_init_hw(chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "hw init failed\n");
+
+	chip->tz_dev = devm_thermal_of_zone_register(&pdev->dev, 0, chip,
+						     &pm8901_tm_zone_ops);
+	if (IS_ERR(chip->tz_dev))
+		return dev_err_probe(&pdev->dev, PTR_ERR(chip->tz_dev),
+				     "thermal zone register failed\n");
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq_alarm, NULL,
+					pm8901_tm_isr, IRQF_ONESHOT,
+					"pm8901-tm-alarm", chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "alarm IRQ request failed\n");
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq_hi_alarm, NULL,
+					pm8901_tm_isr, IRQF_ONESHOT,
+					"pm8901-tm-hi-alarm", chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "hi-alarm IRQ request failed\n");
+
+	platform_set_drvdata(pdev, chip);
+
+	/*
+	 * All resources that we need on the thermal hot path are now in
+	 * place; hand thermal-shutdown responsibility from the PMIC's
+	 * hardware auto-cut to the kernel thermal core. If this fails the
+	 * PMIC is left with its original (post-reset) HW auto-cut intact,
+	 * so we never leave the part unprotected.
+	 */
+	ret = pm8901_tm_enable_sw_override(chip);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to enable SW thermal override\n");
+
+	thermal_zone_device_update(chip->tz_dev, THERMAL_EVENT_UNSPECIFIED);
+
+	{
+		unsigned int stage, thresh;
+		int temp;
+
+		/*
+		 * IRQs and thermal-core polling are live by now, so the
+		 * cached state can be updated under chip->lock at any time.
+		 * Snapshot under the lock so the boot banner is consistent.
+		 */
+		mutex_lock(&chip->lock);
+		stage = chip->stage;
+		thresh = chip->thresh;
+		temp = chip->temp;
+		mutex_unlock(&chip->lock);
+
+		dev_info(&pdev->dev,
+			 "PM8901 thermal alarm: base=0x%x stage=%u thresh=%u temp=%d\n",
+			 chip->base, stage, thresh, temp);
+	}
+
+	return 0;
+}
+
+/*
+ * No explicit ->remove() needed: pm8901_tm_restore_hw_shutdown() is
+ * registered as a devm action in probe and re-enables the PMIC's HW
+ * auto-cut automatically on unbind.
+ */
+
+static const struct of_device_id pm8901_tm_match_table[] = {
+	{ .compatible = "qcom,pm8901-temp-alarm" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pm8901_tm_match_table);
+
+static struct platform_driver pm8901_tm_driver = {
+	.driver = {
+		.name		= "pm8901-temp-alarm",
+		.of_match_table	= pm8901_tm_match_table,
+	},
+	.probe	= pm8901_tm_probe,
+};
+module_platform_driver(pm8901_tm_driver);
+
+MODULE_ALIAS("platform:pm8901-temp-alarm");
+MODULE_DESCRIPTION("Qualcomm PM8901 PMIC Thermal Alarm driver");
+MODULE_LICENSE("GPL v2");
-- 
2.43.0


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

end of thread, other threads:[~2026-05-31  4:09 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
2026-05-30 14:00 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-05-30 14:00 ` [PATCH 0/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
2026-05-30 20:48   ` Rob Herring (Arm)
2026-05-30 14:00 ` [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
2026-05-31  4:09 ` [PATCH v2 0/3] " Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 1/3] dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 2/3] dt-bindings: thermal: qcom: add pm8901-temp-alarm Herman van Hazendonk
2026-05-31  4:09   ` [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk

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