* [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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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, ®);
+ 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, ®);
+ 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, ®);
+ 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 &= ~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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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 8:00 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
` (3 subsequent siblings)
5 siblings, 1 reply; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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 7:59 ` Krzysztof Kozlowski
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, 1 reply; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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, ®);
+ 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, ®);
+ 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, ®);
+ 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, ®);
+ 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, ®);
+ 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] 16+ messages in thread
* Re: [PATCH v2 1/3] dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: mfd: qcom-pm8xxx: allow temp-alarm subnode Herman van Hazendonk
@ 2026-05-31 7:59 ` Krzysztof Kozlowski
0 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-31 7:59 UTC (permalink / raw)
To: Herman van Hazendonk, 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, Zhang Rui
On 31/05/2026 06:09, Herman van Hazendonk wrote:
> 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(+)
>
No clue why MFD patch is in interconnect thread...
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
@ 2026-05-31 8:00 ` Krzysztof Kozlowski
0 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-31 8:00 UTC (permalink / raw)
To: Herman van Hazendonk, Conor Dooley, devicetree, Georgi Djakov,
Krzysztof Kozlowski, linux-arm-msm, linux-kernel, linux-pm,
Rob Herring
On 31/05/2026 06:09, Herman van Hazendonk wrote:
> 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>
And why interconnects are part of thermal...
Do not attach (thread) your patchsets to some other threads (unrelated
or older versions). This buries them deep in the mailbox and might
interfere with applying entire sets. See also:
https://elixir.bootlin.com/linux/v6.16-rc2/source/Documentation/process/submitting-patches.rst#L830
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-05-31 8:00 UTC | newest]
Thread overview: 16+ 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 8:00 ` Krzysztof Kozlowski
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 7:59 ` Krzysztof Kozlowski
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