* [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
@ 2026-05-30 13:58 ` Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 2/3] dt-bindings: reset: qcom: add mmcc-msm8660 reset IDs Herman van Hazendonk
` (14 subsequent siblings)
15 siblings, 0 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:58 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-kernel, linux-arm-msm,
linux-clk, devicetree
Cc: Herman van Hazendonk
Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Multimedia Clock Controller (MMCC). The
header enumerates the clocks and power-domains consumed by the
multimedia subsystem (MDP4 display, Adreno A220 GPU, CAMSS image
pipeline, VFE, Gemini JPEG, video codec, rotator, VPE and the GFX2D
Z180 cores).
IDs intentionally match the numeric values used by the original
shared mmcc-msm8960.h so the driver's clk array indexing is preserved;
only the clocks actually implemented by mmcc-msm8660.c are defined.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
include/dt-bindings/clock/qcom,mmcc-msm8660.h | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 include/dt-bindings/clock/qcom,mmcc-msm8660.h
diff --git a/include/dt-bindings/clock/qcom,mmcc-msm8660.h b/include/dt-bindings/clock/qcom,mmcc-msm8660.h
new file mode 100644
index 000000000000..00c3a75e8b71
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,mmcc-msm8660.h
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Clock and power-domain bindings for the MSM8x60 family (MSM8260/MSM8660/APQ8060)
+ * Multimedia Clock Controller (MMCC).
+ *
+ * MSM8260, MSM8660 and APQ8060 are the same Scorpion-class MSM8x60 SoC
+ * with different bin/feature labels. MSM8960 is a newer generation (Krait)
+ * — its bindings live in
+ * <dt-bindings/clock/qcom,mmcc-msm8960.h> and must not be reused here.
+ *
+ * IDs below intentionally match the numeric values used by the original
+ * shared mmcc-msm8960.h so the driver's clk array indexing is preserved;
+ * only the clocks actually implemented by mmcc-msm8660.c are defined.
+ */
+
+#ifndef _DT_BINDINGS_CLK_MSM_MMCC_8660_H
+#define _DT_BINDINGS_CLK_MSM_MMCC_8660_H
+
+#define TV_ENC_AHB_CLK 3
+#define AMP_AHB_CLK 4
+#define JPEGD_AHB_CLK 6
+#define GFX2D0_AHB_CLK 7
+#define DSI_S_AHB_CLK 8
+#define VPE_AHB_CLK 10
+#define SMMU_AHB_CLK 11
+#define HDMI_M_AHB_CLK 12
+#define VFE_AHB_CLK 13
+#define ROT_AHB_CLK 14
+#define VCODEC_AHB_CLK 15
+#define MDP_AHB_CLK 16
+#define DSI_M_AHB_CLK 17
+#define CSI0_AHB_CLK 18
+#define MMSS_IMEM_AHB_CLK 19
+#define IJPEG_AHB_CLK 20
+#define HDMI_S_AHB_CLK 21
+#define GFX3D_AHB_CLK 22
+#define GFX2D1_AHB_CLK 23
+#define JPEGD_AXI_CLK 28
+#define GMEM_AXI_CLK 29
+#define MDP_AXI_CLK 30
+#define MMSS_IMEM_AXI_CLK 31
+#define IJPEG_AXI_CLK 32
+#define GFX3D_AXI_CLK 33
+#define VCODEC_AXI_CLK 34
+#define VFE_AXI_CLK 35
+#define VPE_AXI_CLK 36
+#define ROT_AXI_CLK 37
+#define VCODEC_AXI_A_CLK 38
+#define VCODEC_AXI_B_CLK 39
+#define CSI0_SRC 47
+#define CSI0_CLK 48
+#define CSI0_PHY_CLK 49
+#define CSI1_SRC 50
+#define CSI1_CLK 51
+#define CSI1_PHY_CLK 52
+#define DSI_SRC 56
+#define DSI_CLK 57
+#define CSI_PIX_CLK 58
+#define CSI_RDI_CLK 59
+#define MDP_VSYNC_CLK 60
+#define HDMI_APP_CLK 62
+#define GFX2D0_SRC 66
+#define GFX2D0_CLK 67
+#define GFX2D1_SRC 68
+#define GFX2D1_CLK 69
+#define GFX3D_SRC 70
+#define GFX3D_CLK 71
+#define IJPEG_SRC 72
+#define IJPEG_CLK 73
+#define JPEGD_SRC 74
+#define JPEGD_CLK 75
+#define MDP_SRC 76
+#define MDP_CLK 77
+#define MDP_LUT_CLK 78
+#define DSI1_BYTE_SRC 83
+#define DSI1_BYTE_CLK 84
+#define DSI1_ESC_SRC 87
+#define DSI1_ESC_CLK 88
+#define ROT_SRC 91
+#define ROT_CLK 92
+#define TV_ENC_CLK 93
+#define TV_DAC_CLK 94
+#define HDMI_TV_CLK 95
+#define MDP_TV_CLK 96
+#define TV_SRC 97
+#define VCODEC_SRC 98
+#define VCODEC_CLK 99
+#define VFE_SRC 100
+#define VFE_CLK 101
+#define VFE_CSI0_CLK 102
+#define VPE_SRC 103
+#define VPE_CLK 104
+#define DSI_PIXEL_SRC 105
+#define DSI_PIXEL_CLK 106
+#define CAMCLK0_SRC 107
+#define CAMCLK0_CLK 108
+#define CAMCLK1_SRC 109
+#define CAMCLK1_CLK 110
+#define CSIPHYTIMER_SRC 113
+#define CSIPHY1_TIMER_CLK 115
+#define CSIPHY0_TIMER_CLK 116
+#define PLL2 118
+#define MDP_PIXEL_SRC 129
+#define MDP_PIXEL_CLK 130
+#define MDP_LCDC_CLK 131
+#define VFE_CSI1_CLK 132
+#define CSI1_AHB_CLK 133
+
+/*
+ * MSM8x60 legacy footswitch power domains.
+ * Used with the MMCC power-domain provider (#power-domain-cells = <1>).
+ * Numbering is independent of the clock ID space above.
+ */
+#define GFX2D0_GDSC 0
+#define GFX2D1_GDSC 1
+#define GFX3D_GDSC 2
+#define IJPEG_GDSC 3
+#define MDP_GDSC 4
+#define ROT_GDSC 5
+#define VED_GDSC 6
+#define VFE_GDSC 7
+#define VPE_GDSC 8
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH 2/3] dt-bindings: reset: qcom: add mmcc-msm8660 reset IDs
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
@ 2026-05-30 13:58 ` Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller Herman van Hazendonk
` (13 subsequent siblings)
15 siblings, 0 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:58 UTC (permalink / raw)
To: Philipp Zabel, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-kernel, linux-arm-msm, devicetree
Cc: Herman van Hazendonk
Add the dt-binding reset-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) MMCC reset lines. The MMCC exposes per-IP
software-reset bits used by MDP4 (display), CAMSS / VFE (camera),
Gemini (JPEG), rotator, VPE and the GFX2D / GFX3D blocks; each gets a
matching reset-controller ID here.
IDs match the numeric values used by the original shared
mmcc-msm8960.h reset bindings; only the resets actually implemented
by mmcc-msm8660.c are defined.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
include/dt-bindings/reset/qcom,mmcc-msm8660.h | 88 +++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 include/dt-bindings/reset/qcom,mmcc-msm8660.h
diff --git a/include/dt-bindings/reset/qcom,mmcc-msm8660.h b/include/dt-bindings/reset/qcom,mmcc-msm8660.h
new file mode 100644
index 000000000000..c3ffd57834c9
--- /dev/null
+++ b/include/dt-bindings/reset/qcom,mmcc-msm8660.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * Reset bindings for the MSM8x60 family (MSM8260/MSM8660/APQ8060) Multimedia Clock
+ * Controller (MMCC).
+ *
+ * MSM8260, MSM8660 and APQ8060 are the same Scorpion-class MSM8x60 SoC
+ * with different bin/feature labels. MSM8960 is a newer generation (Krait)
+ * — its reset bindings live in
+ * <dt-bindings/reset/qcom,mmcc-msm8960.h> and must not be reused here.
+ *
+ * IDs intentionally match the numeric values used by the original shared
+ * mmcc-msm8960.h so the driver's qcom_reset_map array indexing is preserved;
+ * only the resets actually implemented by mmcc-msm8660.c are defined.
+ */
+
+#ifndef _DT_BINDINGS_RESET_MSM_MMCC_8660_H
+#define _DT_BINDINGS_RESET_MSM_MMCC_8660_H
+
+#define VPE_AXI_RESET 0
+#define IJPEG_AXI_RESET 1
+#define MPD_AXI_RESET 2
+#define VFE_AXI_RESET 3
+#define SP_AXI_RESET 4
+#define VCODEC_AXI_RESET 5
+#define ROT_AXI_RESET 6
+#define VCODEC_AXI_A_RESET 7
+#define VCODEC_AXI_B_RESET 8
+#define FAB_S3_AXI_RESET 9
+#define FAB_S2_AXI_RESET 10
+#define FAB_S1_AXI_RESET 11
+#define FAB_S0_AXI_RESET 12
+#define SMMU_GFX3D_ABH_RESET 13
+#define SMMU_VPE_AHB_RESET 14
+#define SMMU_VFE_AHB_RESET 15
+#define SMMU_ROT_AHB_RESET 16
+#define SMMU_VCODEC_B_AHB_RESET 17
+#define SMMU_VCODEC_A_AHB_RESET 18
+#define SMMU_MDP1_AHB_RESET 19
+#define SMMU_MDP0_AHB_RESET 20
+#define SMMU_JPEGD_AHB_RESET 21
+#define SMMU_IJPEG_AHB_RESET 22
+#define APU_AHB_RESET 25
+#define CSI_AHB_RESET 26
+#define TV_ENC_AHB_RESET 27
+#define VPE_AHB_RESET 28
+#define FABRIC_AHB_RESET 29
+#define GFX2D0_AHB_RESET 30
+#define GFX2D1_AHB_RESET 31
+#define GFX3D_AHB_RESET 32
+#define HDMI_AHB_RESET 33
+#define MSSS_IMEM_AHB_RESET 34
+#define IJPEG_AHB_RESET 35
+#define DSI_M_AHB_RESET 36
+#define DSI_S_AHB_RESET 37
+#define JPEGD_AHB_RESET 38
+#define MDP_AHB_RESET 39
+#define ROT_AHB_RESET 40
+#define VCODEC_AHB_RESET 41
+#define VFE_AHB_RESET 42
+#define CSIPHY0_RESET 47
+#define CSIPHY1_RESET 48
+#define VFE_CSI_RESET 50
+#define MDP_RESET 51
+#define AMP_RESET 52
+#define JPEGD_RESET 53
+#define CSI1_RESET 54
+#define VPE_RESET 55
+#define MMSS_FABRIC_RESET 56
+#define VFE_RESET 57
+#define GFX3D_RESET 60
+#define HDMI_RESET 61
+#define MMSS_IMEM_RESET 62
+#define IJPEG_RESET 63
+#define CSI0_RESET 64
+#define DSI_RESET 65
+#define VCODEC_RESET 66
+#define MDP_TV_RESET 67
+#define MDP_VSYNC_RESET 68
+#define ROT_RESET 69
+#define TV_HDMI_RESET 70
+#define TV_ENC_RESET 71
+#define GFX3D_AXI_RESET 75
+#define CSI_RDI_RESET 79
+#define CSI_PIX_RESET 80
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 2/3] dt-bindings: reset: qcom: add mmcc-msm8660 reset IDs Herman van Hazendonk
@ 2026-05-30 13:59 ` Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
` (12 subsequent siblings)
15 siblings, 0 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Philipp Zabel, Rob Herring, Stephen Boyd
Hi all,
This series adds the Multimedia Clock Controller (MMCC) driver for the
MSM8x60 family of SoCs (MSM8260/MSM8660/APQ8060) - the Scorpion-class
generation that preceded MSM8960's Krait CPUs. It also introduces the
clock-ID and reset-ID device-tree binding headers that the MMCC consumer
nodes will reference.
The MMCC layout on MSM8x60 differs from MSM8960 in several ways that
make a separate driver cleaner than parameterising mmcc-msm8960.c, most
notably:
- the pix_rdi mux requires a custom set_parent op that temporarily
enables both parents during the glitch-free transition;
- the IJPEG GDSC requires releasing AXI, AHB and CORE resets;
- several rate-source pairs (MDP pixel, GFX2D/3D) only exist on 8x60
(e.g. PLL2-derived 228571000/266667000 for graphics);
- the camera CSI / VFE / JPEG / VPE / ROT clock topology lacks the
later 8960 reorganisation.
Used on the HP TouchPad (Tenderloin) for graphics (Adreno A220),
display (MDP4), camera (CSI/VFE), JPEG (Gemini), VIDC, VPE and rotator.
The driver compiles cleanly against current arm-msm/for-next. The two
new binding headers are dual-licensed (GPL-2.0-only OR BSD-2-Clause)
per current qcom-binding convention.
A companion series adds the LPASS Clock Controller (LCC) for the same
SoC family.
Tested on HP TouchPad. Full board DTS will be sent once this and the
other foundation series (interconnect, irqchip MPM, gcc cleanup) are
in -next.
Thanks,
Herman
Herman van Hazendonk (3):
dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs
dt-bindings: reset: qcom: add mmcc-msm8660 reset IDs
clk: qcom: add MSM8x60 MMCC driver
drivers/clk/qcom/Kconfig | 11 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/mmcc-msm8660.c | 2998 +++++++++++++++++
include/dt-bindings/clock/qcom,mmcc-msm8660.h | 126 +
include/dt-bindings/reset/qcom,mmcc-msm8660.h | 88 +
5 files changed, 3224 insertions(+)
create mode 100644 drivers/clk/qcom/mmcc-msm8660.c
create mode 100644 include/dt-bindings/clock/qcom,mmcc-msm8660.h
create mode 100644 include/dt-bindings/reset/qcom,mmcc-msm8660.h
--
2.43.0
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller
@ 2026-05-30 13:59 Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
` (15 more replies)
0 siblings, 16 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Hi all,
This series adds the LPASS (Low Power Audio SubSystem) Clock Controller
driver for the MSM8x60 family of SoCs (MSM8260/MSM8660/APQ8060) - the
Scorpion-class generation that preceded MSM8960's Krait CPUs.
The register layout, parent muxing and divider topology of the LPASS
PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
driver is simpler than parameterising lcc-msm8960.c. Both drivers can
coexist (different Kconfig, match table and compatible).
Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP needs
functional MI2S / SLIMBus / PCM clocks before audio playback or capture
works.
The new binding header is dual-licensed (GPL-2.0-only OR BSD-2-Clause)
per current qcom-binding convention.
Companion to the MSM8x60 MMCC series.
Thanks,
Herman
Herman van Hazendonk (2):
dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
clk: qcom: add MSM8x60 LCC (LPASS) driver
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/lcc-msm8660.c | 517 +++++++++++++++++++
include/dt-bindings/clock/qcom,lcc-msm8660.h | 48 ++
4 files changed, 575 insertions(+)
create mode 100644 drivers/clk/qcom/lcc-msm8660.c
create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h
--
2.43.0
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (2 preceding siblings ...)
2026-05-30 13:59 ` [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller Herman van Hazendonk
@ 2026-05-30 13:59 ` Herman van Hazendonk
2026-05-30 14:15 ` sashiko-bot
2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
` (11 subsequent siblings)
15 siblings, 1 reply; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Low Power Audio SubSystem Clock Controller
(LCC). The header enumerates the LPASS clocks consumed by the
qcom,apq8060-lpaif sound card and the codec/AIF nodes downstream of
it. It mirrors the format and ID range of the existing LCC headers
for newer Qualcomm SoCs (lcc-msm8960, lcc-msm8974) so the
drivers/clk/qcom/lcc-msm8660.c driver can be hooked up the same way
once it lands.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
include/dt-bindings/clock/qcom,lcc-msm8660.h | 48 ++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h
diff --git a/include/dt-bindings/clock/qcom,lcc-msm8660.h b/include/dt-bindings/clock/qcom,lcc-msm8660.h
new file mode 100644
index 000000000000..d5d9b0d71a78
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,lcc-msm8660.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _DT_BINDINGS_CLK_LCC_MSM8660_H
+#define _DT_BINDINGS_CLK_LCC_MSM8660_H
+
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller (LCC)
+ * clock IDs. These are compatible with MSM8960 LCC as MSM8x60 and
+ * MSM8960 share the same audio subsystem clock architecture.
+ */
+
+#define PLL4 0
+#define MI2S_OSR_SRC 1
+#define MI2S_OSR_CLK 2
+#define MI2S_DIV_CLK 3
+#define MI2S_BIT_DIV_CLK 4
+#define MI2S_BIT_CLK 5
+#define PCM_SRC 6
+#define PCM_CLK_OUT 7
+#define PCM_CLK 8
+#define SLIMBUS_SRC 9
+#define AUDIO_SLIMBUS_CLK 10
+#define SPS_SLIMBUS_CLK 11
+#define CODEC_I2S_MIC_OSR_SRC 12
+#define CODEC_I2S_MIC_OSR_CLK 13
+#define CODEC_I2S_MIC_DIV_CLK 14
+#define CODEC_I2S_MIC_BIT_DIV_CLK 15
+#define CODEC_I2S_MIC_BIT_CLK 16
+#define SPARE_I2S_MIC_OSR_SRC 17
+#define SPARE_I2S_MIC_OSR_CLK 18
+#define SPARE_I2S_MIC_DIV_CLK 19
+#define SPARE_I2S_MIC_BIT_DIV_CLK 20
+#define SPARE_I2S_MIC_BIT_CLK 21
+#define CODEC_I2S_SPKR_OSR_SRC 22
+#define CODEC_I2S_SPKR_OSR_CLK 23
+#define CODEC_I2S_SPKR_DIV_CLK 24
+#define CODEC_I2S_SPKR_BIT_DIV_CLK 25
+#define CODEC_I2S_SPKR_BIT_CLK 26
+#define SPARE_I2S_SPKR_OSR_SRC 27
+#define SPARE_I2S_SPKR_OSR_CLK 28
+#define SPARE_I2S_SPKR_DIV_CLK 29
+#define SPARE_I2S_SPKR_BIT_DIV_CLK 30
+#define SPARE_I2S_SPKR_BIT_CLK 31
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (3 preceding siblings ...)
2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
@ 2026-05-30 13:59 ` Herman van Hazendonk
2026-05-30 14:25 ` sashiko-bot
2026-05-30 14:00 ` [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
` (10 subsequent siblings)
15 siblings, 1 reply; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 13:59 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Add a clock driver for the LPASS (Low Power Audio SubSystem) Clock
Controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060) - the
Scorpion-class generation that preceded MSM8960's Krait CPUs.
The register layout, parent muxing and divider topology of the LPASS
PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
driver is simpler than parameterising mmcc-msm8960.c. Both drivers
can coexist in tree (different KConfig options, different match
table, different device-tree compatible).
Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP
needs functional MI2S / SLIMBus / PCM clocks before audio playback
or capture works.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/lcc-msm8660.c | 517 +++++++++++++++++++++++++++++++++
3 files changed, 527 insertions(+)
create mode 100644 drivers/clk/qcom/lcc-msm8660.c
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index d9cff5b0281d..2ea95f69355e 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -547,6 +547,15 @@ config MSM_LCC_8960
Say Y if you want to use audio devices such as i2s, pcm,
SLIMBus, etc.
+config MSM_LCC_8660
+ tristate "MSM8x60 LPASS Clock Controller"
+ depends on ARM || COMPILE_TEST
+ help
+ Support for the LPASS clock controller on the MSM8x60 family
+ (MSM8260/MSM8660/APQ8060). MSM8960 is the newer
+ Krait-based generation handled separately by MSM_LCC_8960.
+ Say Y if you want to use audio devices such as i2s, pcm, SLIMBus.
+
config MDM_GCC_9607
tristate "MDM9607 Global Clock Controller"
depends on ARM || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index e100cfd6a52d..41c973d7db59 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
obj-$(CONFIG_MSM_GCC_8976) += gcc-msm8976.o
obj-$(CONFIG_MSM_GCC_8994) += gcc-msm8994.o
obj-$(CONFIG_MSM_GCC_8996) += gcc-msm8996.o
+obj-$(CONFIG_MSM_LCC_8660) += lcc-msm8660.o
obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o
obj-$(CONFIG_MSM_GCC_8998) += gcc-msm8998.o
obj-$(CONFIG_MSM_GPUCC_8998) += gpucc-msm8998.o
diff --git a/drivers/clk/qcom/lcc-msm8660.c b/drivers/clk/qcom/lcc-msm8660.c
new file mode 100644
index 000000000000..7f13279467f9
--- /dev/null
+++ b/drivers/clk/qcom/lcc-msm8660.c
@@ -0,0 +1,517 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller driver.
+ *
+ * Split from lcc-msm8960.c because the MSM8x60 family is a separate
+ * SoC generation (Scorpion) from MSM8960 (Krait). The clock topology is
+ * compatible but PLL4 runs at a different rate (540.672 MHz, L=22) and the
+ * driver has no need for the MDM9615 CXO patch or the 492 MHz frequency plan.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,lcc-msm8660.h>
+
+#include "common.h"
+#include "clk-regmap.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-branch.h"
+#include "clk-regmap-divider.h"
+#include "clk-regmap-mux.h"
+
+static struct clk_pll pll4 = {
+ .l_reg = 0x4,
+ .m_reg = 0x8,
+ .n_reg = 0xc,
+ .config_reg = 0x14,
+ .mode_reg = 0x0,
+ .status_reg = 0x18,
+ .status_bit = 16,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pll4",
+ .parent_data = &(const struct clk_parent_data){
+ .fw_name = "pxo", .name = "pxo_board",
+ },
+ .num_parents = 1,
+ .ops = &clk_pll_ops,
+ },
+};
+
+enum {
+ P_PXO,
+ P_PLL4,
+};
+
+static const struct parent_map lcc_pxo_pll4_map[] = {
+ { P_PXO, 0 },
+ { P_PLL4, 2 }
+};
+
+static const struct clk_parent_data lcc_pxo_pll4[] = {
+ { .fw_name = "pxo", .name = "pxo_board" },
+ { .fw_name = "pll4_vote", .name = "pll4_vote" },
+};
+
+/*
+ * MSM8x60 PLL4 runs at 540.672 MHz (24.576 MHz * 22, L=0x16).
+ * Divisors taken from the legacy webOS clock-8x60.c driver.
+ * AIF_OSR has an 8-bit M/N counter, so 512000 Hz is not achievable with
+ * this PLL frequency and is intentionally omitted from the 540 MHz tables.
+ */
+static const struct freq_tbl clk_tbl_aif_osr_540[] = {
+ { 768000, P_PLL4, 4, 1, 176 },
+ { 1024000, P_PLL4, 4, 1, 132 },
+ { 1536000, P_PLL4, 4, 1, 88 },
+ { 2048000, P_PLL4, 4, 1, 66 },
+ { 3072000, P_PLL4, 4, 1, 44 },
+ { 4096000, P_PLL4, 4, 1, 33 },
+ { 6144000, P_PLL4, 4, 1, 22 },
+ { 8192000, P_PLL4, 2, 1, 33 },
+ { 12288000, P_PLL4, 4, 1, 11 },
+ { 24576000, P_PLL4, 2, 1, 11 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+static const struct freq_tbl clk_tbl_aif_osr_393[] = {
+ { 512000, P_PLL4, 4, 1, 192 },
+ { 768000, P_PLL4, 4, 1, 128 },
+ { 1024000, P_PLL4, 4, 1, 96 },
+ { 1536000, P_PLL4, 4, 1, 64 },
+ { 2048000, P_PLL4, 4, 1, 48 },
+ { 3072000, P_PLL4, 4, 1, 32 },
+ { 4096000, P_PLL4, 4, 1, 24 },
+ { 6144000, P_PLL4, 4, 1, 16 },
+ { 8192000, P_PLL4, 4, 1, 12 },
+ { 12288000, P_PLL4, 4, 1, 8 },
+ { 24576000, P_PLL4, 4, 1, 4 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+#define CLK_AIF_OSR_SRC(prefix, _ns, _md) \
+static struct clk_rcg prefix##_osr_src = { \
+ .ns_reg = _ns, \
+ .md_reg = _md, \
+ .mn = { \
+ .mnctr_en_bit = 8, \
+ .mnctr_reset_bit = 7, \
+ .mnctr_mode_shift = 5, \
+ .n_val_shift = 24, \
+ .m_val_shift = 8, \
+ .width = 8, \
+ }, \
+ .p = { \
+ .pre_div_shift = 3, \
+ .pre_div_width = 2, \
+ }, \
+ .s = { \
+ .src_sel_shift = 0, \
+ .parent_map = lcc_pxo_pll4_map, \
+ }, \
+ .freq_tbl = clk_tbl_aif_osr_393, \
+ .clkr = { \
+ .enable_reg = _ns, \
+ .enable_mask = BIT(9), \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_osr_src", \
+ .parent_data = lcc_pxo_pll4, \
+ .num_parents = ARRAY_SIZE(lcc_pxo_pll4), \
+ .ops = &clk_rcg_ops, \
+ .flags = CLK_SET_RATE_GATE, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_CLK(prefix, _ns, hr, en_bit) \
+static struct clk_branch prefix##_osr_clk = { \
+ .halt_reg = hr, \
+ .halt_bit = 1, \
+ .halt_check = BRANCH_HALT_ENABLE, \
+ .clkr = { \
+ .enable_reg = _ns, \
+ .enable_mask = BIT(en_bit), \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_osr_clk", \
+ .parent_hws = (const struct clk_hw*[]){ \
+ &prefix##_osr_src.clkr.hw, \
+ }, \
+ .num_parents = 1, \
+ .ops = &clk_branch_ops, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_DIV_CLK(prefix, _ns, _width) \
+static struct clk_regmap_div prefix##_div_clk = { \
+ .reg = _ns, \
+ .shift = 10, \
+ .width = _width, \
+ .clkr = { \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_div_clk", \
+ .parent_hws = (const struct clk_hw*[]){ \
+ &prefix##_osr_src.clkr.hw, \
+ }, \
+ .num_parents = 1, \
+ .ops = &clk_regmap_div_ops, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, en_bit) \
+static struct clk_branch prefix##_bit_div_clk = { \
+ .halt_reg = hr, \
+ .halt_bit = 0, \
+ .halt_check = BRANCH_HALT_ENABLE, \
+ .clkr = { \
+ .enable_reg = _ns, \
+ .enable_mask = BIT(en_bit), \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_bit_div_clk", \
+ .parent_hws = (const struct clk_hw*[]){ \
+ &prefix##_div_clk.clkr.hw, \
+ }, \
+ .num_parents = 1, \
+ .ops = &clk_branch_ops, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_BIT_CLK(prefix, _ns, _shift) \
+static struct clk_regmap_mux prefix##_bit_clk = { \
+ .reg = _ns, \
+ .shift = _shift, \
+ .width = 1, \
+ .clkr = { \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_bit_clk", \
+ .parent_data = (const struct clk_parent_data[]){ \
+ { .hw = &prefix##_bit_div_clk.clkr.hw, }, \
+ { .fw_name = #prefix "_codec_clk", \
+ .name = #prefix "_codec_clk", }, \
+ }, \
+ .num_parents = 2, \
+ .ops = &clk_regmap_mux_closest_ops, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+ }, \
+};
+
+CLK_AIF_OSR_SRC(mi2s, 0x48, 0x4c)
+CLK_AIF_OSR_CLK(mi2s, 0x48, 0x50, 17)
+CLK_AIF_OSR_DIV_CLK(mi2s, 0x48, 4)
+CLK_AIF_OSR_BIT_DIV_CLK(mi2s, 0x48, 0x50, 15)
+CLK_AIF_OSR_BIT_CLK(mi2s, 0x48, 14)
+
+/*
+ * CLK_AIF_OSR_DIV - Audio Interface with divider clocks.
+ * Enable bits per legacy MSM8660 kernel:
+ * - OSR branch enable: BIT(17)
+ * - BIT_DIV branch enable: BIT(15)
+ */
+#define CLK_AIF_OSR_DIV(prefix, _ns, _md, hr) \
+ CLK_AIF_OSR_SRC(prefix, _ns, _md) \
+ CLK_AIF_OSR_CLK(prefix, _ns, hr, 17) \
+ CLK_AIF_OSR_DIV_CLK(prefix, _ns, 8) \
+ CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, 15) \
+ CLK_AIF_OSR_BIT_CLK(prefix, _ns, 18)
+
+CLK_AIF_OSR_DIV(codec_i2s_mic, 0x60, 0x64, 0x68);
+CLK_AIF_OSR_DIV(spare_i2s_mic, 0x78, 0x7c, 0x80);
+CLK_AIF_OSR_DIV(codec_i2s_spkr, 0x6c, 0x70, 0x74);
+CLK_AIF_OSR_DIV(spare_i2s_spkr, 0x84, 0x88, 0x8c);
+
+/* PCM frequency table for MSM8x60 with PLL4 at 540.672 MHz */
+static const struct freq_tbl clk_tbl_pcm_540[] = {
+ { 256000, P_PLL4, 4, 1, 528 },
+ { 512000, P_PLL4, 4, 1, 264 },
+ { 768000, P_PLL4, 4, 1, 176 },
+ { 1024000, P_PLL4, 4, 1, 132 },
+ { 1536000, P_PLL4, 4, 1, 88 },
+ { 2048000, P_PLL4, 4, 1, 66 },
+ { 3072000, P_PLL4, 4, 1, 44 },
+ { 4096000, P_PLL4, 4, 1, 33 },
+ { 6144000, P_PLL4, 4, 1, 22 },
+ { 8192000, P_PLL4, 2, 1, 33 },
+ { 12288000, P_PLL4, 4, 1, 11 },
+ { 24576000, P_PLL4, 2, 1, 11 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+static const struct freq_tbl clk_tbl_pcm_393[] = {
+ { 256000, P_PLL4, 4, 1, 384 },
+ { 512000, P_PLL4, 4, 1, 192 },
+ { 768000, P_PLL4, 4, 1, 128 },
+ { 1024000, P_PLL4, 4, 1, 96 },
+ { 1536000, P_PLL4, 4, 1, 64 },
+ { 2048000, P_PLL4, 4, 1, 48 },
+ { 3072000, P_PLL4, 4, 1, 32 },
+ { 4096000, P_PLL4, 4, 1, 24 },
+ { 6144000, P_PLL4, 4, 1, 16 },
+ { 8192000, P_PLL4, 4, 1, 12 },
+ { 12288000, P_PLL4, 4, 1, 8 },
+ { 24576000, P_PLL4, 4, 1, 4 },
+ { }
+};
+
+static struct clk_rcg pcm_src = {
+ .ns_reg = 0x54,
+ .md_reg = 0x58,
+ .mn = {
+ .mnctr_en_bit = 8,
+ .mnctr_reset_bit = 7,
+ .mnctr_mode_shift = 5,
+ .n_val_shift = 16,
+ .m_val_shift = 16,
+ .width = 16,
+ },
+ .p = {
+ .pre_div_shift = 3,
+ .pre_div_width = 2,
+ },
+ .s = {
+ .src_sel_shift = 0,
+ .parent_map = lcc_pxo_pll4_map,
+ },
+ .freq_tbl = clk_tbl_pcm_393,
+ .clkr = {
+ .enable_reg = 0x54,
+ .enable_mask = BIT(9),
+ .hw.init = &(struct clk_init_data){
+ .name = "pcm_src",
+ .parent_data = lcc_pxo_pll4,
+ .num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+ .ops = &clk_rcg_ops,
+ .flags = CLK_SET_RATE_GATE,
+ },
+ },
+};
+
+static struct clk_branch pcm_clk_out = {
+ .halt_reg = 0x5c,
+ .halt_bit = 0,
+ .halt_check = BRANCH_HALT_ENABLE,
+ .clkr = {
+ .enable_reg = 0x54,
+ .enable_mask = BIT(11),
+ .hw.init = &(struct clk_init_data){
+ .name = "pcm_clk_out",
+ .parent_hws = (const struct clk_hw*[]){
+ &pcm_src.clkr.hw
+ },
+ .num_parents = 1,
+ .ops = &clk_branch_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_regmap_mux pcm_clk = {
+ .reg = 0x54,
+ .shift = 10,
+ .width = 1,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "pcm_clk",
+ .parent_data = (const struct clk_parent_data[]){
+ { .hw = &pcm_clk_out.clkr.hw },
+ { .fw_name = "pcm_codec_clk", .name = "pcm_codec_clk" },
+ },
+ .num_parents = 2,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_rcg slimbus_src = {
+ .ns_reg = 0xcc,
+ .md_reg = 0xd0,
+ .mn = {
+ .mnctr_en_bit = 8,
+ .mnctr_reset_bit = 7,
+ .mnctr_mode_shift = 5,
+ .n_val_shift = 24,
+ .m_val_shift = 8,
+ .width = 8,
+ },
+ .p = {
+ .pre_div_shift = 3,
+ .pre_div_width = 2,
+ },
+ .s = {
+ .src_sel_shift = 0,
+ .parent_map = lcc_pxo_pll4_map,
+ },
+ .freq_tbl = clk_tbl_aif_osr_393,
+ .clkr = {
+ .enable_reg = 0xcc,
+ .enable_mask = BIT(9),
+ .hw.init = &(struct clk_init_data){
+ .name = "slimbus_src",
+ .parent_data = lcc_pxo_pll4,
+ .num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+ .ops = &clk_rcg_ops,
+ .flags = CLK_SET_RATE_GATE,
+ },
+ },
+};
+
+static struct clk_branch audio_slimbus_clk = {
+ .halt_reg = 0xd4,
+ .halt_bit = 0,
+ .halt_check = BRANCH_HALT_ENABLE,
+ .clkr = {
+ .enable_reg = 0xcc,
+ .enable_mask = BIT(10),
+ .hw.init = &(struct clk_init_data){
+ .name = "audio_slimbus_clk",
+ .parent_hws = (const struct clk_hw*[]){
+ &slimbus_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .ops = &clk_branch_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_branch sps_slimbus_clk = {
+ .halt_reg = 0xd4,
+ .halt_bit = 1,
+ .halt_check = BRANCH_HALT_ENABLE,
+ .clkr = {
+ .enable_reg = 0xcc,
+ .enable_mask = BIT(12),
+ .hw.init = &(struct clk_init_data){
+ .name = "sps_slimbus_clk",
+ .parent_hws = (const struct clk_hw*[]){
+ &slimbus_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .ops = &clk_branch_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_regmap *lcc_msm8660_clks[] = {
+ [PLL4] = &pll4.clkr,
+ [MI2S_OSR_SRC] = &mi2s_osr_src.clkr,
+ [MI2S_OSR_CLK] = &mi2s_osr_clk.clkr,
+ [MI2S_DIV_CLK] = &mi2s_div_clk.clkr,
+ [MI2S_BIT_DIV_CLK] = &mi2s_bit_div_clk.clkr,
+ [MI2S_BIT_CLK] = &mi2s_bit_clk.clkr,
+ [PCM_SRC] = &pcm_src.clkr,
+ [PCM_CLK_OUT] = &pcm_clk_out.clkr,
+ [PCM_CLK] = &pcm_clk.clkr,
+ [SLIMBUS_SRC] = &slimbus_src.clkr,
+ [AUDIO_SLIMBUS_CLK] = &audio_slimbus_clk.clkr,
+ [SPS_SLIMBUS_CLK] = &sps_slimbus_clk.clkr,
+ [CODEC_I2S_MIC_OSR_SRC] = &codec_i2s_mic_osr_src.clkr,
+ [CODEC_I2S_MIC_OSR_CLK] = &codec_i2s_mic_osr_clk.clkr,
+ [CODEC_I2S_MIC_DIV_CLK] = &codec_i2s_mic_div_clk.clkr,
+ [CODEC_I2S_MIC_BIT_DIV_CLK] = &codec_i2s_mic_bit_div_clk.clkr,
+ [CODEC_I2S_MIC_BIT_CLK] = &codec_i2s_mic_bit_clk.clkr,
+ [SPARE_I2S_MIC_OSR_SRC] = &spare_i2s_mic_osr_src.clkr,
+ [SPARE_I2S_MIC_OSR_CLK] = &spare_i2s_mic_osr_clk.clkr,
+ [SPARE_I2S_MIC_DIV_CLK] = &spare_i2s_mic_div_clk.clkr,
+ [SPARE_I2S_MIC_BIT_DIV_CLK] = &spare_i2s_mic_bit_div_clk.clkr,
+ [SPARE_I2S_MIC_BIT_CLK] = &spare_i2s_mic_bit_clk.clkr,
+ [CODEC_I2S_SPKR_OSR_SRC] = &codec_i2s_spkr_osr_src.clkr,
+ [CODEC_I2S_SPKR_OSR_CLK] = &codec_i2s_spkr_osr_clk.clkr,
+ [CODEC_I2S_SPKR_DIV_CLK] = &codec_i2s_spkr_div_clk.clkr,
+ [CODEC_I2S_SPKR_BIT_DIV_CLK] = &codec_i2s_spkr_bit_div_clk.clkr,
+ [CODEC_I2S_SPKR_BIT_CLK] = &codec_i2s_spkr_bit_clk.clkr,
+ [SPARE_I2S_SPKR_OSR_SRC] = &spare_i2s_spkr_osr_src.clkr,
+ [SPARE_I2S_SPKR_OSR_CLK] = &spare_i2s_spkr_osr_clk.clkr,
+ [SPARE_I2S_SPKR_DIV_CLK] = &spare_i2s_spkr_div_clk.clkr,
+ [SPARE_I2S_SPKR_BIT_DIV_CLK] = &spare_i2s_spkr_bit_div_clk.clkr,
+ [SPARE_I2S_SPKR_BIT_CLK] = &spare_i2s_spkr_bit_clk.clkr,
+};
+
+static const struct regmap_config lcc_msm8660_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xfc,
+ .fast_io = true,
+};
+
+static const struct qcom_cc_desc lcc_msm8660_desc = {
+ .config = &lcc_msm8660_regmap_config,
+ .clks = lcc_msm8660_clks,
+ .num_clks = ARRAY_SIZE(lcc_msm8660_clks),
+};
+
+static const struct of_device_id lcc_msm8660_match_table[] = {
+ { .compatible = "qcom,lcc-msm8660" },
+ { .compatible = "qcom,lcc-apq8060" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lcc_msm8660_match_table);
+
+static int lcc_msm8660_probe(struct platform_device *pdev)
+{
+ struct regmap *regmap;
+ u32 val;
+
+ regmap = qcom_cc_map(pdev, &lcc_msm8660_desc);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /*
+ * MSM8x60 should always boot with PLL4 L=22 (540.672 MHz).
+ * Detect anyway so a board with a non-standard L value still gets a
+ * coherent frequency plan instead of silently producing wrong rates.
+ */
+ regmap_read(regmap, 0x4, &val);
+ if (val == 0x16) {
+ dev_info(&pdev->dev,
+ "PLL4 L=0x%x, using 540MHz frequency plan\n", val);
+ slimbus_src.freq_tbl = clk_tbl_aif_osr_540;
+ mi2s_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+ codec_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+ spare_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+ codec_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+ spare_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_540;
+ pcm_src.freq_tbl = clk_tbl_pcm_540;
+ } else {
+ dev_info(&pdev->dev,
+ "PLL4 L=0x%x, using fallback 393MHz frequency plan\n",
+ val);
+ }
+
+ /* Enable PLL4 source on the LPASS Primary PLL Mux */
+ regmap_write(regmap, 0xc4, 0x1);
+
+ return qcom_cc_really_probe(&pdev->dev, &lcc_msm8660_desc, regmap);
+}
+
+static struct platform_driver lcc_msm8660_driver = {
+ .probe = lcc_msm8660_probe,
+ .driver = {
+ .name = "lcc-msm8660",
+ .of_match_table = lcc_msm8660_match_table,
+ },
+};
+module_platform_driver(lcc_msm8660_driver);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 LPASS Clock Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lcc-msm8660");
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (4 preceding siblings ...)
2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
@ 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
` (9 subsequent siblings)
15 siblings, 0 replies; 46+ 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] 46+ messages in thread
* [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (5 preceding siblings ...)
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
` (8 subsequent siblings)
15 siblings, 0 replies; 46+ 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] 46+ messages in thread
* [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (6 preceding siblings ...)
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:14 ` sashiko-bot
2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
` (7 subsequent siblings)
15 siblings, 1 reply; 46+ 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] 46+ messages in thread
* [PATCH 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (7 preceding siblings ...)
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 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
` (6 subsequent siblings)
15 siblings, 0 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Konrad Dybcio,
Krzysztof Kozlowski, linux-arm-msm, linux-kernel, Rob Herring,
Thomas Gleixner, van Hazendonk
Add the binding for the Modem Power Manager (MPM) interrupt
controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The MPM
is a small wake-capable interrupt aggregator that lets the SoC stay
in low-power states while a small set of GIC SPIs continues to be
monitored and can wake the system on an edge.
The binding describes its register region (in the RPM syscon), the
GIC SPI it raises on a wake event, the GIC mailbox it shares with
the RPM, and the per-pin wake-source mapping table.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../qcom,msm8660-mpm.yaml | 122 ++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
new file mode 100644
index 000000000000..72f9c2be03b2
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/qcom,msm8660-mpm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm MSM8x60 MPM wakeup interrupt controller
+
+maintainers:
+ - Herman van Hazendonk <github.com@herrie.org>
+
+description: |
+ The MSM Power Manager (MPM) on the MSM8x60 family (MSM8260/MSM8660/
+ APQ8060) is an always-on hardware block that keeps a set of wake
+ interrupts alive while the application processor is powered down.
+ On MSM8x60 the vMPM registers live INSIDE the RPM control block
+ (request regs at RPM_BASE + 0x9d8, status at RPM_BASE + 0xdf8), with
+ notification of new wake-source configuration delivered via the
+ qcom-apcs-ipc mailbox (writing GCC + 0x008 bit 1).
+
+ This binding is MSM8x60-specific. Newer Qualcomm SoCs (sm6375, etc.)
+ use the qcom,mpm binding which assumes a dedicated MPM SRAM region
+ and an IPCC mailbox — neither holds on MSM8x60. The driver lives at
+ drivers/irqchip/irq-msm8660-mpm.c.
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: qcom,msm8660-mpm
+
+ interrupts:
+ maxItems: 1
+ description:
+ IPC IRQ raised by MPM when one of the enabled wake sources fires.
+ On MSM8x60 this is wired to GIC SPI 2.
+
+ qcom,rpm-syscon:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the RPM control block exposed as a syscon. The vMPM
+ registers are accessed at offsets within this block.
+
+ qcom,mpm-offset:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Offset of the vMPM register window within the RPM syscon. On
+ MSM8x60 this is 0x9d8. Status registers are at this offset + 0x420
+ (== 0xdf8 from RPM base).
+
+ mboxes:
+ maxItems: 1
+ description:
+ Mailbox channel used to notify MPM that the vMPM request
+ registers have been updated. On MSM8x60 this is the
+ qcom-apcs-ipc mailbox channel 1 (writes GCC + 0x008 bit 1).
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+ description:
+ First cell is the MPM pin number / parent GIC SPI; second cell
+ is the trigger type.
+
+ qcom,mpm-pin-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Total number of MPM pins exposed.
+
+ qcom,mpm-pin-map:
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ items:
+ items:
+ - description: MPM pin number
+ - description: GIC SPI number this pin maps to
+ description: |
+ List of (MPM-pin, GIC-SPI) tuples for wake sources that have a
+ corresponding GIC IRQ. Consumers route their interrupts through
+ the MPM hierarchical irqdomain to take advantage of these
+ mappings.
+
+ Raw wake pins like SDC3/4 DATx (pins 21-24) are NOT listed
+ here — they have no GIC SPI mapping and are accessed through
+ the msm8660_mpm_set_pin_wake() / msm8660_mpm_enable_pin() C API
+ by consumer drivers.
+
+required:
+ - compatible
+ - interrupts
+ - qcom,rpm-syscon
+ - qcom,mpm-offset
+ - mboxes
+ - interrupt-controller
+ - '#interrupt-cells'
+ - qcom,mpm-pin-count
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ soc {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ msm8660_mpm: interrupt-controller {
+ compatible = "qcom,msm8660-mpm";
+ qcom,rpm-syscon = <&rpm>;
+ qcom,mpm-offset = <0x9d8>;
+ interrupts = <GIC_SPI 2 IRQ_TYPE_EDGE_RISING>;
+ mboxes = <&gcc_ipc 1>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupt-parent = <&intc>;
+ qcom,mpm-pin-count = <64>;
+ qcom,mpm-pin-map = <25 100>,
+ <26 50>,
+ <27 79>;
+ };
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (8 preceding siblings ...)
2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
2026-05-30 14:22 ` sashiko-bot
2026-05-30 14:00 ` [PATCH 0/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
` (5 subsequent siblings)
15 siblings, 1 reply; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Konrad Dybcio,
Krzysztof Kozlowski, linux-arm-msm, linux-kernel, Rob Herring,
Thomas Gleixner, van Hazendonk
Add a driver for the MSM Power Manager (MPM) on the MSM8x60 family
(MSM8260/MSM8660/APQ8060). The MPM is a small wake-source controller
implemented in the always-on power domain that latches edge-triggered
interrupts during APPS power collapse and signals the RPM to wake the
APPS back up.
The driver:
- implements an irqchip that wraps a parent GIC SPI line and
overrides ->irq_set_wake() to enable the MPM mirror of the same
interrupt;
- exposes msm8660_mpm_set_pin_wake() etc. for consumers (e.g.
mmci's SDC4 SDIO wake) that do not flow through irq_set_wake()
directly;
- communicates with the RPM via SMSM/syscon to apply the wake
enable/clear bits and request a power collapse vote.
Used on the HP TouchPad (Tenderloin) for SDIO-attached Wi-Fi and
panel touch wake-from-suspend.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/irqchip/Kconfig | 23 ++
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-msm8660-mpm.c | 627 ++++++++++++++++++++++++++++++
include/soc/qcom/msm8660-mpm.h | 80 ++++
4 files changed, 731 insertions(+)
create mode 100644 drivers/irqchip/irq-msm8660-mpm.c
create mode 100644 include/soc/qcom/msm8660-mpm.h
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e755a2a05209..35598a56ac79 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -525,6 +525,29 @@ config QCOM_MPM
MSM Power Manager driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
+config QCOM_MSM8660_MPM
+ bool "MSM8x60 MPM wakeup interrupt controller"
+ depends on ARCH_QCOM
+ depends on MFD_SYSCON
+ depends on MAILBOX
+ select IRQ_DOMAIN_HIERARCHY
+ default y
+ help
+ Platform driver for the MSM Power Manager (MPM) wakeup interrupt
+ controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The vMPM registers live inside
+ the RPM control block, which makes the generic QCOM_MPM driver
+ unusable here (it assumes a dedicated MPM SRAM region, an IPCC
+ mailbox, and uses IRQCHIP_DECLARE early-init that races platform
+ device creation).
+
+ This driver replicates the legacy 2.6.35-palm arch/arm/mach-msm/
+ mpm.c mechanism as a regular platform driver, accessing the vMPM
+ registers via a syscon phandle to the RPM block and signaling the
+ MPM via the qcom-apcs-ipc mailbox (writing to GCC + 0x008 bit 1).
+
+ Required for cpuidle deep-sleep states (SPC/PC) and for
+ WoWLAN-style wake-from-suspend on MSM8x60 SoCs.
+
config CSKY_MPINTC
bool
depends on CSKY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 26aa3b6ec99f..3e64591f0f5b 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o
obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
obj-$(CONFIG_QCOM_MPM) += irq-qcom-mpm.o
+obj-$(CONFIG_QCOM_MSM8660_MPM) += irq-msm8660-mpm.o
obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
obj-$(CONFIG_RISCV_INTC) += irq-riscv-intc.o
diff --git a/drivers/irqchip/irq-msm8660-mpm.c b/drivers/irqchip/irq-msm8660-mpm.c
new file mode 100644
index 000000000000..c579d1476e01
--- /dev/null
+++ b/drivers/irqchip/irq-msm8660-mpm.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM (MSM Power Manager) wakeup interrupt controller
+ *
+ * The MPM is an always-on hardware block that keeps a small set of wake
+ * sources alive while the application processor is powered down for
+ * cpuidle Power Collapse or suspend-to-RAM. On MSM8x60 the
+ * vMPM (virtual MPM) registers live INSIDE the RPM's 4 KB control block
+ * at:
+ *
+ * request (control) regs: RPM_BASE + 0x9d8 (ENABLE, DETECT_CTL,
+ * POLARITY, CLEAR)
+ * status (pending) regs: RPM_BASE + 0xdf8 (== 0x9d8 + 0x420)
+ *
+ * The mainline qcom-mpm driver (drivers/irqchip/irq-qcom-mpm.c) is
+ * fundamentally incompatible with this layout:
+ * - it assumes a dedicated MPM SRAM region separate from RPM;
+ * - it assumes a mailbox controller (IPCC) for wake notification;
+ * - it uses IRQCHIP_DECLARE which runs before platform devices exist,
+ * so of_find_device_by_node() returns NULL and the init silently
+ * hangs.
+ *
+ * This driver replicates the 2.6.35-palm `arch/arm/mach-msm/mpm.c`
+ * mechanism as a regular platform driver: probes after platform
+ * infrastructure is ready, accesses RPM via a syscon phandle, and uses
+ * the qcom-apcs-ipc mailbox for wake notification (writing to
+ * GCC + 0x008 bit 1).
+ *
+ * Two consumer interfaces:
+ *
+ * 1. Hierarchical irqdomain: for MPM pins that map to GIC SPIs (USB,
+ * HDMI, ...). Consumers wire their interrupts through this
+ * controller via interrupts-extended and the kernel manages
+ * enable / mask / set_type via the IRQ subsystem. This is the
+ * mainline-recommended path for IRQ-mappable wake sources.
+ *
+ * 2. Raw-pin API: for MPM pins that do NOT correspond to a GIC IRQ
+ * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24).
+ * These are physical wake-signal lines monitored by MPM
+ * directly. Consumers (mmci for SDC4 wake) call
+ * msm8660_mpm_set_pin_wake() etc. to twiddle the MPM enable
+ * register for these "raw" pins.
+ *
+ * Copyright (c) 2026, Herman van Hazendonk <github.com@herrie.org>
+ * Copyright (c) 2010-2012, The Linux Foundation (legacy mpm.c reference)
+ */
+
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mailbox_client.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+
+#include <soc/qcom/msm8660-mpm.h>
+
+/* vMPM register offsets (relative to RPM base + 0x9d8). Each register is
+ * a window of two 32-bit slots since MPM exposes 64 wake pins.
+ */
+#define MSM8660_MPM_REG_ENABLE 0x00
+#define MSM8660_MPM_REG_DETECT_CTL 0x08
+#define MSM8660_MPM_REG_POLARITY 0x10
+#define MSM8660_MPM_REG_CLEAR 0x18
+
+/* Status registers at +0x420 from vMPM base (== RPM + 0xdf8). */
+#define MSM8660_MPM_STATUS_OFFSET 0x420
+
+#define MSM8660_MPM_PIN_COUNT 64
+#define MSM8660_MPM_REG_WIDTH 2
+
+struct msm8660_mpm_pin {
+ int pin;
+ int hwirq;
+};
+
+struct msm8660_mpm {
+ struct device *dev;
+ struct regmap *regmap;
+ unsigned int regmap_offset;
+ struct irq_domain *domain;
+ struct msm8660_mpm_pin *pin_map;
+ unsigned int pin_map_count;
+ int parent_irq;
+ raw_spinlock_t lock;
+ struct mbox_client mbox_client;
+ struct mbox_chan *mbox_chan;
+};
+
+/* Singleton — there is only one MPM instance per SoC. The pin-API
+ * lookup helpers (msm8660_mpm_get()) return this.
+ */
+static struct msm8660_mpm *msm8660_mpm_global;
+
+static u32 msm8660_mpm_read(struct msm8660_mpm *mpm, unsigned int reg)
+{
+ u32 val;
+ regmap_read(mpm->regmap, mpm->regmap_offset + reg, &val);
+ return val;
+}
+
+static void msm8660_mpm_write(struct msm8660_mpm *mpm, unsigned int reg,
+ u32 val)
+{
+ regmap_write(mpm->regmap, mpm->regmap_offset + reg, val);
+}
+
+static int msm8660_mpm_pin_to_hwirq(struct msm8660_mpm *mpm, int pin)
+{
+ int i;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].pin == pin)
+ return mpm->pin_map[i].hwirq;
+ }
+ return -ENOENT;
+}
+
+/*
+ * IPC handler: MPM fires this IRQ when one or more enabled wake pins
+ * have pending activity. Read pending status, for each pin look up the
+ * corresponding GIC hwirq (if mapped), replay it through the
+ * irqdomain, then clear the pending bit.
+ */
+static irqreturn_t msm8660_mpm_irq(int irq, void *data)
+{
+ struct msm8660_mpm *mpm = data;
+ unsigned long pending[MSM8660_MPM_REG_WIDTH];
+ unsigned long enable[MSM8660_MPM_REG_WIDTH];
+ int i, j;
+
+ for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
+ pending[i] = msm8660_mpm_read(mpm,
+ MSM8660_MPM_STATUS_OFFSET + i * 4);
+ enable[i] = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + i * 4);
+ }
+
+ for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
+ unsigned long bits = pending[i] & enable[i];
+
+ for_each_set_bit(j, &bits, 32) {
+ int pin = i * 32 + j;
+ int hwirq = msm8660_mpm_pin_to_hwirq(mpm, pin);
+
+ if (hwirq >= 0) {
+ dev_dbg(mpm->dev, "wake pin %d -> hwirq %d\n",
+ pin, hwirq);
+ generic_handle_domain_irq(mpm->domain, hwirq);
+ }
+ }
+
+ if (bits)
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_CLEAR + i * 4, bits);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void msm8660_mpm_enable_hwirq(struct irq_data *d, bool enable)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ int i, pin = -1;
+ u32 val;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].hwirq == d->hwirq) {
+ pin = mpm->pin_map[i].pin;
+ break;
+ }
+ }
+
+ if (pin < 0)
+ return;
+
+ raw_spin_lock_irqsave(&mpm->lock, flags);
+
+ val = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
+ if (enable)
+ val |= BIT(pin % 32);
+ else
+ val &= ~BIT(pin % 32);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
+
+ raw_spin_unlock_irqrestore(&mpm->lock, flags);
+}
+
+static void msm8660_mpm_mask_irq(struct irq_data *d)
+{
+ msm8660_mpm_enable_hwirq(d, false);
+ irq_chip_mask_parent(d);
+}
+
+static void msm8660_mpm_unmask_irq(struct irq_data *d)
+{
+ msm8660_mpm_enable_hwirq(d, true);
+ irq_chip_unmask_parent(d);
+}
+
+static int msm8660_mpm_set_type(struct irq_data *d, unsigned int type)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ unsigned long flags;
+ int i, pin = -1;
+ u32 detect, polarity;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].hwirq == d->hwirq) {
+ pin = mpm->pin_map[i].pin;
+ break;
+ }
+ }
+
+ if (pin < 0)
+ return -ENOENT;
+
+ raw_spin_lock_irqsave(&mpm->lock, flags);
+
+ detect = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
+ polarity = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ detect |= BIT(pin % 32);
+ polarity |= BIT(pin % 32);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ detect |= BIT(pin % 32);
+ polarity &= ~BIT(pin % 32);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ detect &= ~BIT(pin % 32);
+ polarity |= BIT(pin % 32);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ detect &= ~BIT(pin % 32);
+ polarity &= ~BIT(pin % 32);
+ break;
+ default:
+ raw_spin_unlock_irqrestore(&mpm->lock, flags);
+ return -EINVAL;
+ }
+
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4, detect);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4, polarity);
+
+ raw_spin_unlock_irqrestore(&mpm->lock, flags);
+
+ return irq_chip_set_type_parent(d, type);
+}
+
+static struct irq_chip msm8660_mpm_chip = {
+ .name = "msm8660-mpm",
+ .irq_mask = msm8660_mpm_mask_irq,
+ .irq_unmask = msm8660_mpm_unmask_irq,
+ .irq_set_type = msm8660_mpm_set_type,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE,
+};
+
+static int msm8660_mpm_domain_alloc(struct irq_domain *domain,
+ unsigned int virq,
+ unsigned int nr_irqs, void *data)
+{
+ struct msm8660_mpm *mpm = domain->host_data;
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ int hwirq, ret;
+
+ if (fwspec->param_count != 2)
+ return -EINVAL;
+
+ hwirq = fwspec->param[0];
+
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ parent_fwspec.param_count = 3;
+ parent_fwspec.param[0] = 0; /* GIC_SPI */
+ parent_fwspec.param[1] = hwirq;
+ parent_fwspec.param[2] = fwspec->param[1];
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+ if (ret)
+ return ret;
+
+ irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
+ &msm8660_mpm_chip, mpm);
+
+ return 0;
+}
+
+static const struct irq_domain_ops msm8660_mpm_domain_ops = {
+ .alloc = msm8660_mpm_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+ .translate = irq_domain_translate_twocell,
+};
+
+/* ===================================================================
+ * Raw-pin API for wake sources without a GIC IRQ mapping (SDC3/4 DATx)
+ * ===================================================================
+ *
+ * These helpers operate on MPM pins directly via the ENABLE / DETECT_CTL
+ * / POLARITY registers. Used by consumer drivers (mmci for SDC4 wake)
+ * that need to monitor a physical signal line via MPM but don't have a
+ * corresponding GIC interrupt to route through the irqdomain.
+ */
+
+/**
+ * msm8660_mpm_get() - get a handle to the MPM device
+ * @np: device node of the consumer (used for phandle lookup)
+ * @propname: phandle property name; if NULL, returns the singleton
+ * without phandle verification.
+ *
+ * Returns the singleton MPM handle, or ERR_PTR(-EPROBE_DEFER) if the
+ * MPM driver hasn't probed yet.
+ */
+struct msm8660_mpm *msm8660_mpm_get(struct device_node *np,
+ const char *propname)
+{
+ struct device_node *mpm_np;
+
+ if (!msm8660_mpm_global)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (np && propname) {
+ mpm_np = of_parse_phandle(np, propname, 0);
+ if (!mpm_np)
+ return ERR_PTR(-ENOENT);
+ of_node_put(mpm_np);
+ }
+
+ return msm8660_mpm_global;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_get);
+
+/**
+ * msm8660_mpm_enable_pin() - enable/disable MPM monitoring of a pin
+ * @mpm: handle from msm8660_mpm_get()
+ * @pin: MPM pin index (0..63)
+ * @enable: true to monitor, false to ignore
+ *
+ * Programs the ENABLE register directly. Intended for "raw" wake pins
+ * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24) that have no
+ * GIC IRQ mapping. For pins that DO have a GIC mapping (in
+ * qcom,mpm-pin-map), use the irqdomain path instead.
+ */
+int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
+ bool enable)
+{
+ unsigned long flags;
+ u32 val;
+
+ if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
+ return -EINVAL;
+
+ raw_spin_lock_irqsave(&mpm->lock, flags);
+ val = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
+ if (enable)
+ val |= BIT(pin % 32);
+ else
+ val &= ~BIT(pin % 32);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
+ raw_spin_unlock_irqrestore(&mpm->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_enable_pin);
+
+/**
+ * msm8660_mpm_set_pin_wake() - mark a pin as wake-capable
+ * @mpm: handle
+ * @pin: MPM pin index
+ * @on: true to allow this pin to wake the system, false to clear.
+ *
+ * Equivalent to msm8660_mpm_enable_pin() on MSM8660 — the hardware has
+ * a single ENABLE register, not separate enable + wake masks. The name
+ * is kept for API parity with the legacy mpm.h interface so consumers
+ * can express the wake-source intent explicitly.
+ */
+int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm, unsigned int pin,
+ bool on)
+{
+ return msm8660_mpm_enable_pin(mpm, pin, on);
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_set_pin_wake);
+
+/**
+ * msm8660_mpm_set_pin_type() - set trigger type for a raw MPM pin
+ * @mpm: handle
+ * @pin: MPM pin index
+ * @flow_type: standard IRQ_TYPE_* constants
+ *
+ * On MSM8660 the trigger config is split across DETECT_CTL (edge vs
+ * level) and POLARITY (rising/high vs falling/low).
+ */
+int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm, unsigned int pin,
+ unsigned int flow_type)
+{
+ unsigned long flags;
+ u32 detect, polarity;
+ bool edge, polarity_high;
+
+ if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
+ return -EINVAL;
+
+ edge = !!(flow_type & IRQ_TYPE_EDGE_BOTH);
+ polarity_high = !!(flow_type & (IRQ_TYPE_EDGE_RISING |
+ IRQ_TYPE_LEVEL_HIGH));
+
+ raw_spin_lock_irqsave(&mpm->lock, flags);
+
+ detect = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
+ polarity = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4);
+
+ if (edge)
+ detect |= BIT(pin % 32);
+ else
+ detect &= ~BIT(pin % 32);
+
+ if (polarity_high)
+ polarity |= BIT(pin % 32);
+ else
+ polarity &= ~BIT(pin % 32);
+
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4, detect);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4, polarity);
+
+ raw_spin_unlock_irqrestore(&mpm->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_set_pin_type);
+
+/* ===================================================================
+ * Platform driver
+ * ===================================================================
+ */
+
+static int msm8660_mpm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct msm8660_mpm *mpm;
+ struct irq_domain *parent_domain;
+ struct device_node *parent_np;
+ int ret, i;
+ u32 offset;
+
+ if (msm8660_mpm_global) {
+ dev_err(dev, "only one MPM instance is supported\n");
+ return -EBUSY;
+ }
+
+ mpm = devm_kzalloc(dev, sizeof(*mpm), GFP_KERNEL);
+ if (!mpm)
+ return -ENOMEM;
+
+ mpm->dev = dev;
+ raw_spin_lock_init(&mpm->lock);
+
+ mpm->regmap = syscon_regmap_lookup_by_phandle(np, "qcom,rpm-syscon");
+ if (IS_ERR(mpm->regmap)) {
+ dev_err(dev, "failed to get RPM syscon: %ld\n",
+ PTR_ERR(mpm->regmap));
+ return PTR_ERR(mpm->regmap);
+ }
+
+ ret = of_property_read_u32(np, "qcom,mpm-offset", &offset);
+ if (ret) {
+ dev_err(dev, "failed to read qcom,mpm-offset: %d\n", ret);
+ return ret;
+ }
+ mpm->regmap_offset = offset;
+
+ dev_info(dev, "vMPM registers at RPM offset 0x%x\n", offset);
+
+ /* Parse pin map (IRQ-mapped wake pins; raw pins like SDC4_DAT1 are
+ * not listed here — they're accessed via the pin-API below).
+ */
+ ret = of_property_count_u32_elems(np, "qcom,mpm-pin-map");
+ if (ret < 0 || ret % 2) {
+ dev_err(dev, "invalid qcom,mpm-pin-map\n");
+ return -EINVAL;
+ }
+
+ mpm->pin_map_count = ret / 2;
+ mpm->pin_map = devm_kcalloc(dev, mpm->pin_map_count,
+ sizeof(*mpm->pin_map), GFP_KERNEL);
+ if (!mpm->pin_map)
+ return -ENOMEM;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ u32 pin, hwirq;
+
+ of_property_read_u32_index(np, "qcom,mpm-pin-map",
+ i * 2, &pin);
+ of_property_read_u32_index(np, "qcom,mpm-pin-map",
+ i * 2 + 1, &hwirq);
+
+ mpm->pin_map[i].pin = pin;
+ mpm->pin_map[i].hwirq = hwirq;
+
+ dev_dbg(dev, "pin map: pin %d -> hwirq %d\n", pin, hwirq);
+ }
+
+ parent_np = of_irq_find_parent(np);
+ if (!parent_np) {
+ dev_err(dev, "failed to find parent interrupt controller\n");
+ return -ENODEV;
+ }
+
+ parent_domain = irq_find_host(parent_np);
+ of_node_put(parent_np);
+ if (!parent_domain) {
+ dev_err(dev, "failed to find parent IRQ domain\n");
+ return -ENODEV;
+ }
+
+ mpm->domain = irq_domain_create_hierarchy(parent_domain, 0,
+ MSM8660_MPM_PIN_COUNT,
+ of_fwnode_handle(np),
+ &msm8660_mpm_domain_ops,
+ mpm);
+ if (!mpm->domain) {
+ dev_err(dev, "failed to create IRQ domain\n");
+ return -ENOMEM;
+ }
+
+ mpm->parent_irq = platform_get_irq(pdev, 0);
+ if (mpm->parent_irq < 0) {
+ ret = mpm->parent_irq;
+ goto err_remove_domain;
+ }
+
+ ret = devm_request_irq(dev, mpm->parent_irq, msm8660_mpm_irq,
+ IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
+ "msm8660-mpm", mpm);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ %d: %d\n",
+ mpm->parent_irq, ret);
+ goto err_remove_domain;
+ }
+
+ /* Mailbox channel for poking MPM to re-read its config (writes to
+ * GCC + 0x008 bit 1 via the qcom-apcs-ipc mailbox).
+ */
+ mpm->mbox_client.dev = dev;
+ mpm->mbox_client.knows_txdone = true;
+ mpm->mbox_chan = mbox_request_channel(&mpm->mbox_client, 0);
+ if (IS_ERR(mpm->mbox_chan)) {
+ dev_warn(dev, "no mailbox channel: %ld (continuing)\n",
+ PTR_ERR(mpm->mbox_chan));
+ mpm->mbox_chan = NULL;
+ }
+
+ platform_set_drvdata(pdev, mpm);
+ msm8660_mpm_global = mpm;
+
+ dev_info(dev, "ready: %d pin mappings, irq=%d\n",
+ mpm->pin_map_count, mpm->parent_irq);
+
+ return 0;
+
+err_remove_domain:
+ irq_domain_remove(mpm->domain);
+ return ret;
+}
+
+static void msm8660_mpm_remove(struct platform_device *pdev)
+{
+ struct msm8660_mpm *mpm = platform_get_drvdata(pdev);
+
+ msm8660_mpm_global = NULL;
+ if (mpm->mbox_chan)
+ mbox_free_channel(mpm->mbox_chan);
+ irq_domain_remove(mpm->domain);
+}
+
+static const struct of_device_id msm8660_mpm_of_match[] = {
+ { .compatible = "qcom,msm8660-mpm" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, msm8660_mpm_of_match);
+
+static struct platform_driver msm8660_mpm_driver = {
+ .probe = msm8660_mpm_probe,
+ .remove = msm8660_mpm_remove,
+ .driver = {
+ .name = "msm8660-mpm",
+ .of_match_table = msm8660_mpm_of_match,
+ },
+};
+
+static int __init msm8660_mpm_init(void)
+{
+ return platform_driver_register(&msm8660_mpm_driver);
+}
+subsys_initcall(msm8660_mpm_init);
+
+static void __exit msm8660_mpm_exit(void)
+{
+ platform_driver_unregister(&msm8660_mpm_driver);
+}
+module_exit(msm8660_mpm_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 MPM wakeup interrupt controller");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/qcom/msm8660-mpm.h b/include/soc/qcom/msm8660-mpm.h
new file mode 100644
index 000000000000..118748ad6960
--- /dev/null
+++ b/include/soc/qcom/msm8660-mpm.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM wake-source consumer interface.
+ *
+ * The MPM driver lives at drivers/irqchip/irq-msm8660-mpm.c. It exposes
+ * TWO interfaces:
+ *
+ * 1. Hierarchical irqdomain (preferred). For wake sources that map to
+ * GIC SPIs (USB1_HS, HDMI, ...). Consumers wire their IRQ through
+ * the MPM via `interrupts-extended = <&msm8660_mpm ...>` in DT and
+ * the IRQ subsystem manages enable / mask / set_type via the
+ * irqdomain alloc path. No explicit C API call needed.
+ *
+ * 2. Raw-pin API (this header). For wake sources that do NOT have a
+ * GIC IRQ mapping: SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23,
+ * SDC4_DAT3=24. These are physical wake-signal lines that MPM
+ * monitors directly. mmci (for SDC4 WiFi wake) obtains a handle
+ * via msm8660_mpm_get() and uses the helpers below.
+ */
+
+#ifndef __SOC_QCOM_MSM8660_MPM_H__
+#define __SOC_QCOM_MSM8660_MPM_H__
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/irq.h>
+
+#define MSM8660_MPM_NR_PINS 64
+
+/* Well-known wake-source pin indices (from the legacy 2.6.35-palm tree). */
+#define MSM8660_MPM_PIN_SDC3_DAT1 21
+#define MSM8660_MPM_PIN_SDC3_DAT3 22
+#define MSM8660_MPM_PIN_SDC4_DAT1 23
+#define MSM8660_MPM_PIN_SDC4_DAT3 24
+
+struct device_node;
+struct msm8660_mpm;
+
+#if IS_ENABLED(CONFIG_QCOM_MSM8660_MPM)
+
+struct msm8660_mpm *msm8660_mpm_get(struct device_node *np,
+ const char *propname);
+
+int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm, unsigned int pin,
+ bool on);
+int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
+ bool enable);
+int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm, unsigned int pin,
+ unsigned int flow_type);
+
+#else /* !CONFIG_QCOM_MSM8660_MPM */
+
+static inline struct msm8660_mpm *
+msm8660_mpm_get(struct device_node *np, const char *propname)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm,
+ unsigned int pin, bool on)
+{
+ return -ENODEV;
+}
+
+static inline int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm,
+ unsigned int pin, bool enable)
+{
+ return -ENODEV;
+}
+
+static inline int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm,
+ unsigned int pin,
+ unsigned int flow_type)
+{
+ return -ENODEV;
+}
+
+#endif /* CONFIG_QCOM_MSM8660_MPM */
+
+#endif /* __SOC_QCOM_MSM8660_MPM_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH 0/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (9 preceding siblings ...)
2026-05-30 14:00 ` [PATCH 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller 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
` (4 subsequent siblings)
15 siblings, 0 replies; 46+ 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] 46+ messages in thread
* [PATCH 1/2] dt-bindings: thermal: qcom: add pm8901-temp-alarm
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (10 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 14:08 ` sashiko-bot
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
` (3 subsequent siblings)
15 siblings, 2 replies; 46+ 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] 46+ messages in thread
* [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (11 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-30 14:16 ` sashiko-bot
2026-05-31 4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (2 subsequent siblings)
15 siblings, 1 reply; 46+ 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] 46+ 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 14:08 ` sashiko-bot
2026-05-30 20:48 ` Rob Herring (Arm)
1 sibling, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-30 14:08 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The binding introduces a child node without updating the parent MFD schema to allow it, and the provided device tree example violates parent requirements.
- [Medium] Meaningful mismatch between the commit message and the actual binding implementation regarding interrupts and parent references.
- [Low] The binding defines a thermal sensor provider but fails to reference the core `thermal-sensor.yaml` schema.
--
commit 547082ca486fab0b441c0b967105187e700c4d9c
Author: Herman van Hazendonk <github.com@herrie.org>
dt-bindings: thermal: qcom: add pm8901-temp-alarm
This commit adds a device tree binding for the temperature-alarm block
inside the Qualcomm PM8901 PMIC. The driver exposes the PM8901
die-temperature trip stages to the thermal framework via a thermal-zone.
> The binding describes the SSBI sub-node address, the GIC interrupt
> the alarm raises on a stage transition, and the parent PMIC
> reference.
[Severity: Medium]
Does the commit message match the binding implementation? The message
states it describes the GIC interrupt and parent PMIC reference, but the
schema actually defines PMIC-internal interrupts (indices 52 and 53), and
relies purely on the standard device tree parent-child hierarchy rather
than a parent PMIC reference property.
> 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 0000000000000..569943b4aedc5
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/thermal/qcom,pm8901-temp-alarm.yaml
[ ... ]
> +properties:
> + compatible:
> + const: qcom,pm8901-temp-alarm
[Severity: Low]
Should this binding include an allOf block containing a $ref to
/schemas/thermal/thermal-sensor.yaml#?
Standard device tree bindings for thermal sensors usually include this
reference to inherit core schema definitions. By omitting it and defining
additionalProperties: false, the schema will reject future common thermal
sensor properties.
[ ... ]
> +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 {
[Severity: Medium]
Will this pass dtbs_check and dt_binding_check?
The qcom,pm8901 parent schema in qcom-pm8xxx.yaml strictly prohibits
undocumented child nodes via additionalProperties: false and requires an
interrupts property on the pmic node.
It seems the qcom,pm8901-temp-alarm node was added without adding a
corresponding temp-alarm@[0-9a-f]+$ pattern to the parent schema.
Additionally, the pmic node in this example lacks the mandatory
interrupts property.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780148149.git.github.com@herrie.org?part=1
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-30 14:00 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-05-30 14:14 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-30 14:14 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] Compilation failure due to undeclared, unprefixed identifiers in node array initializers.
- [High] Clock rate calculation uses per-node buswidth and caches globally-shared clock rate into node-local state, causing incorrect clock frequencies.
- [High] Use-After-Free of RPM device data due to prematurely dropping device reference without establishing a device link.
- [High] Missing handling of -EPROBE_DEFER permanently cripples bandwidth scaling on the platform.
- [High] Arbitration collision due to `mmfab_to_appss` incorrectly claiming master port 2.
- [High] Bandwidth calculation divides bytes by 8, resulting in severe bus starvation.
--
commit dbe665a3a4a2b6e23a2d9c951ce890ab7021e52a
Author: Herman van Hazendonk <github.com@herrie.org>
Subject: interconnect: qcom: add MSM8x60 NoC driver
This commit adds a Qualcomm interconnect driver for the MSM8x60 family
SoCs. It models the four fabrics and implements the interconnect-provider
API to allow consumer drivers to request bandwidth via icc_set_bw(). The
driver translates these requests to RPM firmware arbitration votes.
> diff --git a/drivers/interconnect/qcom/msm8660.c b/drivers/interconnect/qcom/msm8660.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/interconnect/qcom/msm8660.c
> @@ -0,0 +1,1008 @@
[ ... ]
> +static struct msm8660_icc_node * const msm8660_afab_nodes[] = {
> + [AFAB_MAS_AMPSS_M0] = &mas_ampss_m0,
[Severity: Critical]
Does this cause a compilation failure?
The internal node ID enum defines values with the MSM8660_ prefix, such as
MSM8660_AFAB_MAS_AMPSS_M0. Using the unprefixed version AFAB_MAS_AMPSS_M0
here in the array initializer appears to reference an undeclared identifier.
[ ... ]
> +/*
> + * 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,
[Severity: High]
Does mmfab_to_appss incorrectly claim master port 2 here?
The block comment for the MMSS Fabric nodes mentions that the APPS_FAB
gateway should be on master port 11, while port 2 is already correctly
assigned to ADM1.
When the arbitration arrays are packed, will this duplicate assignment
overwrite the arbitration vote for port 2 and completely deprive ADM DMA
of its bandwidth scaling on the MMSS fabric?
[ ... ]
> +static void msm8660_rpm_commit(struct msm8660_icc_provider *qp)
> +{
[ ... ]
> + 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 */
[Severity: High]
Does icc_units_to_bps() return bandwidth in bytes per second rather than
bits per second?
If bw_bytes is already in bytes, dividing it by 8 here would cause the
driver to request only 1/8th of the necessary bandwidth from the firmware,
which might lead to severe bus starvation.
[ ... ]
> +static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
> +{
> + struct msm8660_icc_node *src_qn;
[ ... ]
> + 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);
[Severity: High]
Could this rate calculation and caching lead to incorrect hardware clock
frequencies?
The code divides the globally aggregated fabric bandwidth by the local bus
width of the specific node that triggered the update (src_qn->buswidth).
Since nodes can have different bus widths, will this cause the calculated
clock rate to oscillate depending on which node requests an update?
Additionally, the globally calculated rate is cached in the local node's
state (src_qn->rate). If another node changes the shared hardware clock
rate in the interim, a subsequent update from this node might skip the
clk_set_rate() call entirely because its local cache matches the new rate,
leaving the hardware stuck at the wrong frequency.
[ ... ]
> +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;
> +}
[Severity: High]
Is it safe to drop the device reference with put_device() while keeping a
pointer to the devres-managed qcom_rpm structure?
Without establishing a device link, the RPM device could be unbound at any
time. If that happens, the qcom_rpm memory would be freed, leaving a
dangling pointer in qp->rpm. A subsequent bandwidth update would then pass
this freed pointer to qcom_rpm_write() in msm8660_rpm_commit(), causing a
use-after-free.
Also, if the RPM device exists but hasn't probed yet, dev_get_drvdata()
returns NULL. Should this function return -EPROBE_DEFER instead of NULL?
Since the interconnect driver registers at core_initcall, returning NULL
swallows the deferral and permanently disables RPM arbitration.
[ ... ]
> +static int msm8660_icc_probe(struct platform_device *pdev)
> +{
[ ... ]
> + /*
> + * 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;
> + }
[Severity: High]
Should this code propagate -EPROBE_DEFER when the clocks are not yet
available?
Because this driver registers at core_initcall, it is very likely to probe
before the clock provider. Catching all errors and continuing with
qp->num_clks = 0 means the driver will silently discard -EPROBE_DEFER,
leaving the platform permanently without clock scaling.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780148149.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
@ 2026-05-30 14:15 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-30 14:15 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The commit introduces a new dt-bindings header for the MSM8x60 LPASS Clock Controller but entirely omits the required update to the corresponding YAML schema (`Documentation/devicetree/bindings/clock/qcom,lcc.yaml`).
--
commit 2ab7502c050fb596d568ab63294755ed48d68189
Author: Herman van Hazendonk <github.com@herrie.org>
dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
This commit introduces the device tree binding clock-ID header for the
MSM8x60 family Low Power Audio SubSystem Clock Controller. The header
enumerates the LPASS clocks to prepare for the new driver.
> diff --git a/include/dt-bindings/clock/qcom,lcc-msm8660.h b/include/dt-bindings/clock/qcom,lcc-msm8660.h
> new file mode 100644
> index 0000000000000..d5d9b0d71a78f
> --- /dev/null
> +++ b/include/dt-bindings/clock/qcom,lcc-msm8660.h
[Severity: Low]
Is there a missing update to the corresponding YAML binding schema?
Documentation/devicetree/bindings/clock/qcom,lcc.yaml currently lists
compatible strings like:
properties:
compatible:
enum:
- qcom,lcc-apq8064
- qcom,lcc-ipq8064
- qcom,lcc-mdm9615
- qcom,lcc-msm8960
Should qcom,lcc-msm8660 be added to this enum and any corresponding if-then
conditional blocks to allow device tree nodes using this compatible string
to pass validation?
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_CLK_LCC_MSM8660_H
> +#define _DT_BINDINGS_CLK_LCC_MSM8660_H
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780148149.git.github.com@herrie.org?part=1
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver
2026-05-30 14:00 ` [PATCH 2/2] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
@ 2026-05-30 14:16 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-30 14:16 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Hardware auto-shutdown override is left enabled if probe fails, permanently disabling PMIC thermal protection.
- [Low] The calculation for the initial uninitialized temperature computes the lower bound of the stage, contradicting the comment that claims it picks the midpoint.
- [Low] Lockless read of shared state variables in `dev_info` during probe.
--
commit 4441da30420e5466a8731b177283d08a1edfc099
Author: Herman van Hazendonk <github.com@herrie.org>
thermal: qcom: add PM8901 PMIC temperature-alarm driver
This commit adds a thermal-of sensor driver for the temperature-alarm block
inside the Qualcomm PM8901 PMIC. It registers interrupts and decodes stages
into temperatures for board thermal zones.
> diff --git a/drivers/thermal/qcom/qcom-pm8901-tm.c b/drivers/thermal/qcom/qcom-pm8901-tm.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/thermal/qcom/qcom-pm8901-tm.c
[ ... ]
> +/*
> + * 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)
> +{
[ ... ]
> + 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;
[Severity: Low]
This isn't a bug, but the comment above states this block picks the "midpoint
of the stage range". Does this formula actually compute the exact lower bound
of the stage instead?
> + else
> + chip->temp = PM8901_TEMP_NO_ALARM;
> + chip->initialised = true;
[ ... ]
> +static int pm8901_tm_init_hw(struct pm8901_tm_chip *chip)
> +{
[ ... ]
> + /*
> + * 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;
[ ... ]
> +static int pm8901_tm_probe(struct platform_device *pdev)
> +{
[ ... ]
> + 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");
[Severity: High]
Because pm8901_tm_init_hw() disables the PMIC's hardware auto-shutdown, if a
subsequent initialization step like devm_thermal_of_zone_register() or IRQ
registration fails, probe() exits with an error.
Since there is no devm action or cleanup path to restore the hardware state
on failure, does this leave the system running with no hardware or software
thermal protection?
> +
> + ret = devm_request_threaded_irq(&pdev->dev, irq_alarm, NULL,
> + pm8901_tm_isr, IRQF_ONESHOT,
> + "pm8901-tm-alarm", chip);
[ ... ]
> + 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);
[Severity: Low]
Since IRQs and the thermal core polling are fully enabled at this point,
could an interrupt or thermal zone update execute concurrently and modify
chip->stage, chip->thresh, or chip->temp?
Does reading these variables here without holding chip->lock risk printing
torn or inconsistent values?
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780148149.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-30 14:00 ` [PATCH 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
@ 2026-05-30 14:22 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-30 14:22 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] The driver requests an IPC mailbox channel to notify the RPM of configuration changes but never sends any messages, rendering wake configurations ineffective.
- [High] Devres teardown sequence mismatch leaves the parent interrupt active after the IRQ domain is freed, leading to a Use-After-Free.
- [High] Calling `regmap_read()` and `regmap_write()` on a syscon regmap from within an irqchip's raw spinlock context causes a sleeping-while-atomic bug on PREEMPT_RT.
- [High] The irqchip fails to implement `.irq_set_wake`, breaking wakeup capabilities for the MPM and its parent GIC.
- [High] Clearing edge-triggered pending interrupts after executing the interrupt handler will drop subsequent edges.
- [Medium] Missing bounds validation on MPM pin indices from Device Tree allows arbitrary out-of-bounds register accesses.
- [Medium] Unsafe consumer API lacks reference counting or device links, leading to UAF if the MPM driver is unbound.
- [Medium] Failure to check the return value of `regmap_read()` results in uninitialized stack memory being used as register state.
--
commit 73a7572c3031147f818620520a565acba0e95ad1
Author: Herman van Hazendonk <github.com@herrie.org>
irqchip: add MSM8x60 MPM wakeup interrupt controller driver
This commit adds a driver for the MSM Power Manager (MPM) on the MSM8x60
family. It implements an irqchip wrapper to configure wake pins and exposes
an API for consumer drivers to manage raw wake signals. The driver interacts
with the RPM via a syscon regmap and IPC mailbox.
> diff --git a/drivers/irqchip/irq-msm8660-mpm.c b/drivers/irqchip/irq-msm8660-mpm.c
> new file mode 100644
> index 0000000000000..c579d1476e01b
> --- /dev/null
> +++ b/drivers/irqchip/irq-msm8660-mpm.c
> @@ -0,0 +1,627 @@
[ ... ]
> +static u32 msm8660_mpm_read(struct msm8660_mpm *mpm, unsigned int reg)
> +{
> + u32 val;
> + regmap_read(mpm->regmap, mpm->regmap_offset + reg, &val);
> + return val;
> +}
[Severity: Medium]
Will this return uninitialized stack memory if the register read fails?
The variable val is uninitialized. If the regmap_read() function fails, it
will not update val, and this function will return stack garbage.
This could result in corrupted state when the read value is treated as
hardware pending status or used in read-modify-write operations.
[ ... ]
> +static irqreturn_t msm8660_mpm_irq(int irq, void *data)
> +{
> + struct msm8660_mpm *mpm = data;
> + unsigned long pending[MSM8660_MPM_REG_WIDTH];
> + unsigned long enable[MSM8660_MPM_REG_WIDTH];
> + int i, j;
> +
> + for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
> + pending[i] = msm8660_mpm_read(mpm,
> + MSM8660_MPM_STATUS_OFFSET + i * 4);
> + enable[i] = msm8660_mpm_read(mpm,
> + MSM8660_MPM_REG_ENABLE + i * 4);
> + }
> +
> + for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
> + unsigned long bits = pending[i] & enable[i];
> +
> + for_each_set_bit(j, &bits, 32) {
> + int pin = i * 32 + j;
> + int hwirq = msm8660_mpm_pin_to_hwirq(mpm, pin);
> +
> + if (hwirq >= 0) {
> + dev_dbg(mpm->dev, "wake pin %d -> hwirq %d\n",
> + pin, hwirq);
> + generic_handle_domain_irq(mpm->domain, hwirq);
> + }
> + }
> +
> + if (bits)
> + msm8660_mpm_write(mpm,
> + MSM8660_MPM_REG_CLEAR + i * 4, bits);
[Severity: High]
Does clearing the pending bits after executing the handler drop subsequent
edges?
If a new edge interrupt arrives on any of these pins while the handler is
executing in generic_handle_domain_irq(), the hardware will latch it again.
Because the write to the CLEAR register is delayed until after the handler
finishes, it will wipe out the newly latched interrupt, permanently losing
the new edge so it is not serviced.
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void msm8660_mpm_enable_hwirq(struct irq_data *d, bool enable)
> +{
> + struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> + int i, pin = -1;
> + u32 val;
> +
> + for (i = 0; i < mpm->pin_map_count; i++) {
> + if (mpm->pin_map[i].hwirq == d->hwirq) {
> + pin = mpm->pin_map[i].pin;
> + break;
> + }
> + }
> +
> + if (pin < 0)
> + return;
> +
> + raw_spin_lock_irqsave(&mpm->lock, flags);
> +
> + val = msm8660_mpm_read(mpm,
> + MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
[Severity: High]
Can this sleepable syscon regmap access while holding a raw spinlock cause a
locking violation?
The core IRQ subsystem invokes irqchip callbacks like irq_mask() and
irq_unmask() with the descriptor's raw_spinlock_t held and preemption
disabled.
Inside these functions, the driver explicitly acquires another raw_spinlock_t
and calls msm8660_mpm_read(), which invokes regmap_read() on the syscon
regmap.
Syscon regmaps use a standard spinlock_t for synchronization, which on
PREEMPT_RT is a sleepable rt_mutex. Attempting to acquire a sleepable lock
while holding a raw_spinlock_t is a locking violation.
> + if (enable)
> + val |= BIT(pin % 32);
> + else
> + val &= ~BIT(pin % 32);
> + msm8660_mpm_write(mpm,
> + MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
[Severity: High]
Should this register update be followed by a mailbox notification to the RPM?
The driver requests an IPC mailbox channel in msm8660_mpm_probe() to signal
configuration changes to the MPM. However, the driver never calls
mbox_send_message() after updating the hardware state here.
Without the doorbell ping, the RPM hardware will not re-evaluate the wake
configuration, which prevents the device from successfully waking the system
from suspend.
> +
> + raw_spin_unlock_irqrestore(&mpm->lock, flags);
> +}
[ ... ]
> +static int msm8660_mpm_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
> + unsigned long flags;
> + int i, pin = -1;
> + u32 detect, polarity;
> +
> + for (i = 0; i < mpm->pin_map_count; i++) {
> + if (mpm->pin_map[i].hwirq == d->hwirq) {
> + pin = mpm->pin_map[i].pin;
> + break;
> + }
> + }
> +
> + if (pin < 0)
> + return -ENOENT;
> +
> + raw_spin_lock_irqsave(&mpm->lock, flags);
> +
> + detect = msm8660_mpm_read(mpm,
> + MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
[Severity: Medium]
Could this result in an out-of-bounds register access if the pin index is
invalid?
The pin value is parsed from the Device Tree in msm8660_mpm_probe() without
any bounds checking. If a malformed or malicious DT specifies a large pin
index, calculating the register offset using (pin / 32) * 4 will result in an
out-of-bounds read and write inside the RPM syscon block.
[ ... ]
> +static struct irq_chip msm8660_mpm_chip = {
> + .name = "msm8660-mpm",
> + .irq_mask = msm8660_mpm_mask_irq,
> + .irq_unmask = msm8660_mpm_unmask_irq,
> + .irq_set_type = msm8660_mpm_set_type,
> + .irq_eoi = irq_chip_eoi_parent,
> + .irq_set_affinity = irq_chip_set_affinity_parent,
> + .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_SKIP_SET_WAKE,
[Severity: High]
Does this missing irq_set_wake callback break system wakeup functionality?
The commit message states the driver "overrides ->irq_set_wake() to enable
the MPM mirror of the same interrupt".
However, the .irq_set_wake callback is omitted here and the flag
IRQCHIP_SKIP_SET_WAKE is applied instead.
When a consumer driver calls enable_irq_wake(), the generic IRQ core will
return 0 and stop propagating the wake request. Neither the MPM hardware nor
the parent GIC will receive the wake configuration.
> +};
[ ... ]
> +struct msm8660_mpm *msm8660_mpm_get(struct device_node *np,
> + const char *propname)
> +{
> + struct device_node *mpm_np;
> +
> + if (!msm8660_mpm_global)
> + return ERR_PTR(-EPROBE_DEFER);
> +
> + if (np && propname) {
> + mpm_np = of_parse_phandle(np, propname, 0);
> + if (!mpm_np)
> + return ERR_PTR(-ENOENT);
> + of_node_put(mpm_np);
> + }
> +
> + return msm8660_mpm_global;
[Severity: Medium]
Does returning the devm-managed pointer here create a use-after-free risk?
This consumer API returns a direct pointer to the devm_kzalloc allocated
structure without establishing a device_link, incrementing module refcounts,
or suppressing bind attributes.
If the MPM driver is unbound, devres will immediately free the structure.
Consumer drivers like mmci will retain a dangling pointer, leading to a
use-after-free when they later call msm8660_mpm_enable_pin().
> +}
> +EXPORT_SYMBOL_GPL(msm8660_mpm_get);
[ ... ]
> +static int msm8660_mpm_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np = dev->of_node;
> + struct msm8660_mpm *mpm;
> + struct irq_domain *parent_domain;
> + struct device_node *parent_np;
> + int ret, i;
> + u32 offset;
[ ... ]
> + for (i = 0; i < mpm->pin_map_count; i++) {
> + u32 pin, hwirq;
> +
> + of_property_read_u32_index(np, "qcom,mpm-pin-map",
> + i * 2, &pin);
> + of_property_read_u32_index(np, "qcom,mpm-pin-map",
> + i * 2 + 1, &hwirq);
> +
> + mpm->pin_map[i].pin = pin;
[Severity: Medium]
Should the pin value be validated against MSM8660_MPM_PIN_COUNT before use?
The pin value is extracted from the Device Tree and stored directly. Missing
bounds validation here allows arbitrary out-of-bounds register accesses later
in functions like msm8660_mpm_set_type() and msm8660_mpm_enable_hwirq().
[ ... ]
> + ret = devm_request_irq(dev, mpm->parent_irq, msm8660_mpm_irq,
> + IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
> + "msm8660-mpm", mpm);
[Severity: High]
Does registering the IRQ handler with devm create a teardown mismatch?
This registers the parent interrupt handler with devm_request_irq(), meaning
it remains active until the devres cleanup phase executes. The devres cleanup
happens after the remove() callback returns.
> + if (ret) {
> + dev_err(dev, "failed to request IRQ %d: %d\n",
> + mpm->parent_irq, ret);
> + goto err_remove_domain;
> + }
> +
> + /* Mailbox channel for poking MPM to re-read its config (writes to
> + * GCC + 0x008 bit 1 via the qcom-apcs-ipc mailbox).
> + */
> + mpm->mbox_client.dev = dev;
> + mpm->mbox_client.knows_txdone = true;
> + mpm->mbox_chan = mbox_request_channel(&mpm->mbox_client, 0);
[Severity: High]
Is the mailbox channel requested here actually used to send messages?
The channel is requested for the purpose of signaling the MPM, but the driver
never calls mbox_send_message() after updating the vMPM registers to trigger
the IPC ping.
[ ... ]
> +static void msm8660_mpm_remove(struct platform_device *pdev)
> +{
> + struct msm8660_mpm *mpm = platform_get_drvdata(pdev);
> +
> + msm8660_mpm_global = NULL;
> + if (mpm->mbox_chan)
> + mbox_free_channel(mpm->mbox_chan);
> + irq_domain_remove(mpm->domain);
[Severity: High]
Can freeing the irq domain manually here cause a use-after-free crash?
The irq_domain_remove() function immediately frees the domain memory, while
the devm-managed msm8660_mpm_irq() handler is still active.
If a hardware interrupt fires between the return of msm8660_mpm_remove() and
devres teardown, the active handler will execute and access the freed domain
pointer in generic_handle_domain_irq().
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780148149.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver
2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
@ 2026-05-30 14:25 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-30 14:25 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Hardware register overlap in `CLK_AIF_OSR_DIV` macro corrupts clock enable states.
- [Medium] Uninitialized variable `val` used if `regmap_read()` fails.
- [Low] Missing 27 MHz PXO entry in `clk_tbl_pcm_393` fallback frequency table.
- [Low] Global state mutation in `lcc_msm8660_probe()` breaks state immutability.
--
commit 757996c6435b09325bf482510e7802bb7053fdf8
Author: Herman van Hazendonk <github.com@herrie.org>
clk: qcom: add MSM8x60 LCC (LPASS) driver
This commit adds a clock driver for the LPASS Clock Controller on the
MSM8x60 family. It introduces a separate driver due to register layout
and parent muxing differences compared to the MSM8960.
> diff --git a/drivers/clk/qcom/lcc-msm8660.c b/drivers/clk/qcom/lcc-msm8660.c
> new file mode 100644
> index 0000000000000..7f13279467f95
> --- /dev/null
> +++ b/drivers/clk/qcom/lcc-msm8660.c
[ ... ]
> +#define CLK_AIF_OSR_DIV_CLK(prefix, _ns, _width) \
> +static struct clk_regmap_div prefix##_div_clk = { \
> + .reg = _ns, \
> + .shift = 10, \
> + .width = _width, \
[ ... ]
> +/*
> + * CLK_AIF_OSR_DIV - Audio Interface with divider clocks.
> + * Enable bits per legacy MSM8660 kernel:
> + * - OSR branch enable: BIT(17)
> + * - BIT_DIV branch enable: BIT(15)
> + */
> +#define CLK_AIF_OSR_DIV(prefix, _ns, _md, hr) \
> + CLK_AIF_OSR_SRC(prefix, _ns, _md) \
> + CLK_AIF_OSR_CLK(prefix, _ns, hr, 17) \
> + CLK_AIF_OSR_DIV_CLK(prefix, _ns, 8) \
> + CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, 15) \
> + CLK_AIF_OSR_BIT_CLK(prefix, _ns, 18)
[Severity: High]
Does this configuration cause a hardware register overlap that corrupts
clock enable states?
The macro configures a clock divider using CLK_AIF_OSR_DIV_CLK with
shift 10 and width 8 on the _ns register, occupying bits 10 through 17.
However, the same macro configures branch clocks on the same _ns register
with enable mask BIT(17) (via CLK_AIF_OSR_CLK) and enable mask BIT(15)
(via CLK_AIF_OSR_BIT_DIV_CLK).
Since clock operations perform read-modify-writes, configuring the clock
divider will overwrite the clock enable bits (15 and 17), and toggling
the branch clocks will corrupt the divider value.
[ ... ]
> +static const struct freq_tbl clk_tbl_pcm_393[] = {
> + { 256000, P_PLL4, 4, 1, 384 },
> + { 512000, P_PLL4, 4, 1, 192 },
> + { 768000, P_PLL4, 4, 1, 128 },
> + { 1024000, P_PLL4, 4, 1, 96 },
> + { 1536000, P_PLL4, 4, 1, 64 },
> + { 2048000, P_PLL4, 4, 1, 48 },
> + { 3072000, P_PLL4, 4, 1, 32 },
> + { 4096000, P_PLL4, 4, 1, 24 },
> + { 6144000, P_PLL4, 4, 1, 16 },
> + { 8192000, P_PLL4, 4, 1, 12 },
> + { 12288000, P_PLL4, 4, 1, 8 },
> + { 24576000, P_PLL4, 4, 1, 4 },
> + { }
> +};
[Severity: Low]
Is the 27 MHz PXO entry missing from this fallback frequency table?
Other frequency tables like clk_tbl_pcm_540 include the
{ 27000000, P_PXO, 1, 0, 0 } entry right before the empty sentinel. Its
omission here might prevent the PCM clock from falling back to the 27 MHz
PXO source when the 393 MHz frequency plan is active.
[ ... ]
> +static int lcc_msm8660_probe(struct platform_device *pdev)
> +{
> + struct regmap *regmap;
> + u32 val;
> +
> + regmap = qcom_cc_map(pdev, &lcc_msm8660_desc);
> + if (IS_ERR(regmap))
> + return PTR_ERR(regmap);
> +
> + /*
> + * MSM8x60 should always boot with PLL4 L=22 (540.672 MHz).
> + * Detect anyway so a board with a non-standard L value still gets a
> + * coherent frequency plan instead of silently producing wrong rates.
> + */
> + regmap_read(regmap, 0x4, &val);
> + if (val == 0x16) {
[Severity: Medium]
Can an uninitialized variable be used here if regmap_read() fails?
The return value of regmap_read() is ignored. If it fails and returns a
negative error code, val will remain uninitialized stack memory. It is
then evaluated in this condition and logged, which could cause undefined
behavior.
> + dev_info(&pdev->dev,
> + "PLL4 L=0x%x, using 540MHz frequency plan\n", val);
> + slimbus_src.freq_tbl = clk_tbl_aif_osr_540;
> + mi2s_osr_src.freq_tbl = clk_tbl_aif_osr_540;
> + codec_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_540;
> + spare_i2s_mic_osr_src.freq_tbl = clk_tbl_aif_osr_540;
> + codec_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_540;
> + spare_i2s_spkr_osr_src.freq_tbl = clk_tbl_aif_osr_540;
> + pcm_src.freq_tbl = clk_tbl_pcm_540;
[Severity: Low]
Could mutating these static global clock structures break state immutability?
This probe dynamically modifies the freq_tbl pointers of static global
clock structures (like slimbus_src and mi2s_osr_src) based on a hardware
register read.
If a subsequent probe executes where val is not 0x16, the else branch
does not restore these pointers to their original 393 tables, causing
the device to incorrectly inherit the mutated 540MHz state. While unlikely
to manifest in a singleton driver with constant hardware values, it
violates the principle of keeping global clock structures immutable.
> + } else {
> + dev_info(&pdev->dev,
> + "PLL4 L=0x%x, using fallback 393MHz frequency plan\n",
> + val);
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780148149.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ 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 14:08 ` sashiko-bot
@ 2026-05-30 20:48 ` Rob Herring (Arm)
1 sibling, 0 replies; 46+ 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] 46+ messages in thread
* [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (12 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:08 ` Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
` (2 more replies)
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
15 siblings, 3 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:08 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Hi all,
Self-review (with Sashiko AI assist) caught five issues in v1 before
maintainer review reached them. v1:
https://lore.kernel.org/linux-arm-msm/cover.1780148149.git.github.com@herrie.org/
v2 changes:
- NEW patch 1/3: extend Documentation/devicetree/bindings/clock/
qcom,lcc.yaml with the three MSM8x60 family compatibles
(qcom,lcc-msm8260, qcom,lcc-msm8660, qcom,lcc-apq8060) and add
them to the existing apq8064/msm8960 conditional block. Without
this, board DTs using the new compatibles fail dt_binding_check.
- patch 2/3: dt-bindings clock-ID header for lcc-msm8660,
unchanged from v1.
- patch 3/3: LCC driver. Five fixes folded in:
* CLK_AIF_OSR_DIV macro: divider width was 8, which made the
bit-divider field span bits 10 through 17 on the _ns
register. On MSM8x60 BIT(15) (BIT_DIV branch enable) and
BIT(17) (OSR branch enable) sit inside that range, so any
clk_regmap_div read-modify-write would clobber both branch
gates. Width 4 (bits 10 through 13) matches the standalone
mi2s div clock and the legacy downstream Samsung MSM8660 /
webOS clock-8x60.c register layout. Added a block comment
documenting the verified bit assignments and the rationale
for the width change.
* Match table: add qcom,lcc-msm8260 alongside the existing
qcom,lcc-msm8660 and qcom,lcc-apq8060. MSM8x60 covers all
three variants.
* clk_tbl_pcm_393: add the 27 MHz PXO fallback entry that
clk_tbl_pcm_540 already carries, so the 393 plan can also
fall back to the board PXO source.
* lcc_msm8660_probe(): check the return value of regmap_read()
on PLL4 L. v1 ignored it, so a regmap failure would have
left val as uninitialised stack memory and steered the rest
of probe down a random branch. Use dev_err_probe() on
failure.
* lcc_msm8660_probe(): assign the freq_tbl pointers on the
static clk_rcg structs unconditionally on both PLL4 plans
instead of mutating-on-540 / leaving-mutated-on-393. The
driver is effectively singleton today, but explicit
assignment also restores the defaults if the driver is ever
rebound on a system whose PLL4 has been reprogrammed.
Thanks,
Herman
Herman van Hazendonk (3):
dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
clk: qcom: add MSM8x60 LCC (LPASS) driver
.../devicetree/bindings/clock/qcom,lcc.yaml | 6 +
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/lcc-msm8660.c | 551 ++++++++++++++++++
include/dt-bindings/clock/qcom,lcc-msm8660.h | 48 ++
5 files changed, 615 insertions(+)
create mode 100644 drivers/clk/qcom/lcc-msm8660.c
create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h
--
2.43.0
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
2026-05-31 4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:14 ` sashiko-bot
2026-05-31 7:58 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
2 siblings, 2 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Add "qcom,lcc-msm8260", "qcom,lcc-msm8660" and "qcom,lcc-apq8060" to
the qcom,lcc enum and to the existing parent-clock-name conditional
block that already covers apq8064/msm8960.
The MSM8x60 family (MSM8260/MSM8660/APQ8060) is the Scorpion-class
generation preceding MSM8960; its LPASS Clock Controller is fed by
the same pxo board source and pll4_vote parents as apq8064/msm8960,
and the codec-fed slave-mode I2S/PCM clocks have the same names, so
the existing apq8064/msm8960 conditional block applies unchanged
once the new compatibles are listed.
This allows board DTs using the new compatibles to pass
dt_binding_check; the actual driver support is added by a later
patch in this series.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
Documentation/devicetree/bindings/clock/qcom,lcc.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/qcom,lcc.yaml b/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
index 55985e562a34..bbdad157f03f 100644
--- a/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
+++ b/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
@@ -12,9 +12,12 @@ maintainers:
properties:
compatible:
enum:
+ - qcom,lcc-apq8060
- qcom,lcc-apq8064
- qcom,lcc-ipq8064
- qcom,lcc-mdm9615
+ - qcom,lcc-msm8260
+ - qcom,lcc-msm8660
- qcom,lcc-msm8960
clocks:
@@ -46,7 +49,10 @@ allOf:
compatible:
contains:
enum:
+ - qcom,lcc-apq8060
- qcom,lcc-apq8064
+ - qcom,lcc-msm8260
+ - qcom,lcc-msm8660
- qcom,lcc-msm8960
then:
properties:
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
2026-05-31 4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:23 ` sashiko-bot
2026-05-31 4:09 ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
2 siblings, 1 reply; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Low Power Audio SubSystem Clock Controller
(LCC). The header enumerates the LPASS clocks consumed by the
qcom,apq8060-lpaif sound card and the codec/AIF nodes downstream of
it. It mirrors the format and ID range of the existing LCC headers
for newer Qualcomm SoCs (lcc-msm8960, lcc-msm8974) so the
drivers/clk/qcom/lcc-msm8660.c driver can be hooked up the same way
once it lands.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
include/dt-bindings/clock/qcom,lcc-msm8660.h | 48 ++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 include/dt-bindings/clock/qcom,lcc-msm8660.h
diff --git a/include/dt-bindings/clock/qcom,lcc-msm8660.h b/include/dt-bindings/clock/qcom,lcc-msm8660.h
new file mode 100644
index 000000000000..d5d9b0d71a78
--- /dev/null
+++ b/include/dt-bindings/clock/qcom,lcc-msm8660.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ */
+
+#ifndef _DT_BINDINGS_CLK_LCC_MSM8660_H
+#define _DT_BINDINGS_CLK_LCC_MSM8660_H
+
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller (LCC)
+ * clock IDs. These are compatible with MSM8960 LCC as MSM8x60 and
+ * MSM8960 share the same audio subsystem clock architecture.
+ */
+
+#define PLL4 0
+#define MI2S_OSR_SRC 1
+#define MI2S_OSR_CLK 2
+#define MI2S_DIV_CLK 3
+#define MI2S_BIT_DIV_CLK 4
+#define MI2S_BIT_CLK 5
+#define PCM_SRC 6
+#define PCM_CLK_OUT 7
+#define PCM_CLK 8
+#define SLIMBUS_SRC 9
+#define AUDIO_SLIMBUS_CLK 10
+#define SPS_SLIMBUS_CLK 11
+#define CODEC_I2S_MIC_OSR_SRC 12
+#define CODEC_I2S_MIC_OSR_CLK 13
+#define CODEC_I2S_MIC_DIV_CLK 14
+#define CODEC_I2S_MIC_BIT_DIV_CLK 15
+#define CODEC_I2S_MIC_BIT_CLK 16
+#define SPARE_I2S_MIC_OSR_SRC 17
+#define SPARE_I2S_MIC_OSR_CLK 18
+#define SPARE_I2S_MIC_DIV_CLK 19
+#define SPARE_I2S_MIC_BIT_DIV_CLK 20
+#define SPARE_I2S_MIC_BIT_CLK 21
+#define CODEC_I2S_SPKR_OSR_SRC 22
+#define CODEC_I2S_SPKR_OSR_CLK 23
+#define CODEC_I2S_SPKR_DIV_CLK 24
+#define CODEC_I2S_SPKR_BIT_DIV_CLK 25
+#define CODEC_I2S_SPKR_BIT_CLK 26
+#define SPARE_I2S_SPKR_OSR_SRC 27
+#define SPARE_I2S_SPKR_OSR_CLK 28
+#define SPARE_I2S_SPKR_DIV_CLK 29
+#define SPARE_I2S_SPKR_BIT_DIV_CLK 30
+#define SPARE_I2S_SPKR_BIT_CLK 31
+
+#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver
2026-05-31 4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:33 ` sashiko-bot
2 siblings, 1 reply; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Conor Dooley, devicetree, Krzysztof Kozlowski,
linux-arm-msm, linux-clk, linux-kernel, Michael Turquette,
Rob Herring, Stephen Boyd
Add a clock driver for the LPASS (Low Power Audio SubSystem) Clock
Controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060) - the
Scorpion-class generation that preceded MSM8960's Krait CPUs.
The register layout, parent muxing and divider topology of the LPASS
PLL/clk fabric differ from MSM8960's LCC enough that a clean separate
driver is simpler than parameterising mmcc-msm8960.c. Both drivers
can coexist in tree (different KConfig options, different match
table, different device-tree compatible).
Used on the HP TouchPad (Tenderloin) where the LPASS Q6 audio DSP
needs functional MI2S / SLIMBus / PCM clocks before audio playback
or capture works.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/clk/qcom/Kconfig | 9 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/lcc-msm8660.c | 551 +++++++++++++++++++++++++++++++++
3 files changed, 561 insertions(+)
create mode 100644 drivers/clk/qcom/lcc-msm8660.c
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index d9cff5b0281d..2ea95f69355e 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -547,6 +547,15 @@ config MSM_LCC_8960
Say Y if you want to use audio devices such as i2s, pcm,
SLIMBus, etc.
+config MSM_LCC_8660
+ tristate "MSM8x60 LPASS Clock Controller"
+ depends on ARM || COMPILE_TEST
+ help
+ Support for the LPASS clock controller on the MSM8x60 family
+ (MSM8260/MSM8660/APQ8060). MSM8960 is the newer
+ Krait-based generation handled separately by MSM_LCC_8960.
+ Say Y if you want to use audio devices such as i2s, pcm, SLIMBus.
+
config MDM_GCC_9607
tristate "MDM9607 Global Clock Controller"
depends on ARM || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index e100cfd6a52d..41c973d7db59 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
obj-$(CONFIG_MSM_GCC_8976) += gcc-msm8976.o
obj-$(CONFIG_MSM_GCC_8994) += gcc-msm8994.o
obj-$(CONFIG_MSM_GCC_8996) += gcc-msm8996.o
+obj-$(CONFIG_MSM_LCC_8660) += lcc-msm8660.o
obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o
obj-$(CONFIG_MSM_GCC_8998) += gcc-msm8998.o
obj-$(CONFIG_MSM_GPUCC_8998) += gpucc-msm8998.o
diff --git a/drivers/clk/qcom/lcc-msm8660.c b/drivers/clk/qcom/lcc-msm8660.c
new file mode 100644
index 000000000000..f71846d493f8
--- /dev/null
+++ b/drivers/clk/qcom/lcc-msm8660.c
@@ -0,0 +1,551 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller driver.
+ *
+ * Split from lcc-msm8960.c because the MSM8x60 family is a separate
+ * SoC generation (Scorpion) from MSM8960 (Krait). The clock topology is
+ * compatible but PLL4 runs at a different rate (540.672 MHz, L=22) and the
+ * driver has no need for the MDM9615 CXO patch or the 492 MHz frequency plan.
+ */
+
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/clk-provider.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,lcc-msm8660.h>
+
+#include "common.h"
+#include "clk-regmap.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-branch.h"
+#include "clk-regmap-divider.h"
+#include "clk-regmap-mux.h"
+
+static struct clk_pll pll4 = {
+ .l_reg = 0x4,
+ .m_reg = 0x8,
+ .n_reg = 0xc,
+ .config_reg = 0x14,
+ .mode_reg = 0x0,
+ .status_reg = 0x18,
+ .status_bit = 16,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pll4",
+ .parent_data = &(const struct clk_parent_data){
+ .fw_name = "pxo", .name = "pxo_board",
+ },
+ .num_parents = 1,
+ .ops = &clk_pll_ops,
+ },
+};
+
+enum {
+ P_PXO,
+ P_PLL4,
+};
+
+static const struct parent_map lcc_pxo_pll4_map[] = {
+ { P_PXO, 0 },
+ { P_PLL4, 2 }
+};
+
+static const struct clk_parent_data lcc_pxo_pll4[] = {
+ { .fw_name = "pxo", .name = "pxo_board" },
+ { .fw_name = "pll4_vote", .name = "pll4_vote" },
+};
+
+/*
+ * MSM8x60 PLL4 runs at 540.672 MHz (24.576 MHz * 22, L=0x16).
+ * Divisors taken from the legacy webOS clock-8x60.c driver.
+ * AIF_OSR has an 8-bit M/N counter, so 512000 Hz is not achievable with
+ * this PLL frequency and is intentionally omitted from the 540 MHz tables.
+ */
+static const struct freq_tbl clk_tbl_aif_osr_540[] = {
+ { 768000, P_PLL4, 4, 1, 176 },
+ { 1024000, P_PLL4, 4, 1, 132 },
+ { 1536000, P_PLL4, 4, 1, 88 },
+ { 2048000, P_PLL4, 4, 1, 66 },
+ { 3072000, P_PLL4, 4, 1, 44 },
+ { 4096000, P_PLL4, 4, 1, 33 },
+ { 6144000, P_PLL4, 4, 1, 22 },
+ { 8192000, P_PLL4, 2, 1, 33 },
+ { 12288000, P_PLL4, 4, 1, 11 },
+ { 24576000, P_PLL4, 2, 1, 11 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+static const struct freq_tbl clk_tbl_aif_osr_393[] = {
+ { 512000, P_PLL4, 4, 1, 192 },
+ { 768000, P_PLL4, 4, 1, 128 },
+ { 1024000, P_PLL4, 4, 1, 96 },
+ { 1536000, P_PLL4, 4, 1, 64 },
+ { 2048000, P_PLL4, 4, 1, 48 },
+ { 3072000, P_PLL4, 4, 1, 32 },
+ { 4096000, P_PLL4, 4, 1, 24 },
+ { 6144000, P_PLL4, 4, 1, 16 },
+ { 8192000, P_PLL4, 4, 1, 12 },
+ { 12288000, P_PLL4, 4, 1, 8 },
+ { 24576000, P_PLL4, 4, 1, 4 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+#define CLK_AIF_OSR_SRC(prefix, _ns, _md) \
+static struct clk_rcg prefix##_osr_src = { \
+ .ns_reg = _ns, \
+ .md_reg = _md, \
+ .mn = { \
+ .mnctr_en_bit = 8, \
+ .mnctr_reset_bit = 7, \
+ .mnctr_mode_shift = 5, \
+ .n_val_shift = 24, \
+ .m_val_shift = 8, \
+ .width = 8, \
+ }, \
+ .p = { \
+ .pre_div_shift = 3, \
+ .pre_div_width = 2, \
+ }, \
+ .s = { \
+ .src_sel_shift = 0, \
+ .parent_map = lcc_pxo_pll4_map, \
+ }, \
+ .freq_tbl = clk_tbl_aif_osr_393, \
+ .clkr = { \
+ .enable_reg = _ns, \
+ .enable_mask = BIT(9), \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_osr_src", \
+ .parent_data = lcc_pxo_pll4, \
+ .num_parents = ARRAY_SIZE(lcc_pxo_pll4), \
+ .ops = &clk_rcg_ops, \
+ .flags = CLK_SET_RATE_GATE, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_CLK(prefix, _ns, hr, en_bit) \
+static struct clk_branch prefix##_osr_clk = { \
+ .halt_reg = hr, \
+ .halt_bit = 1, \
+ .halt_check = BRANCH_HALT_ENABLE, \
+ .clkr = { \
+ .enable_reg = _ns, \
+ .enable_mask = BIT(en_bit), \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_osr_clk", \
+ .parent_hws = (const struct clk_hw*[]){ \
+ &prefix##_osr_src.clkr.hw, \
+ }, \
+ .num_parents = 1, \
+ .ops = &clk_branch_ops, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_DIV_CLK(prefix, _ns, _width) \
+static struct clk_regmap_div prefix##_div_clk = { \
+ .reg = _ns, \
+ .shift = 10, \
+ .width = _width, \
+ .clkr = { \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_div_clk", \
+ .parent_hws = (const struct clk_hw*[]){ \
+ &prefix##_osr_src.clkr.hw, \
+ }, \
+ .num_parents = 1, \
+ .ops = &clk_regmap_div_ops, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, en_bit) \
+static struct clk_branch prefix##_bit_div_clk = { \
+ .halt_reg = hr, \
+ .halt_bit = 0, \
+ .halt_check = BRANCH_HALT_ENABLE, \
+ .clkr = { \
+ .enable_reg = _ns, \
+ .enable_mask = BIT(en_bit), \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_bit_div_clk", \
+ .parent_hws = (const struct clk_hw*[]){ \
+ &prefix##_div_clk.clkr.hw, \
+ }, \
+ .num_parents = 1, \
+ .ops = &clk_branch_ops, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+ }, \
+}; \
+
+#define CLK_AIF_OSR_BIT_CLK(prefix, _ns, _shift) \
+static struct clk_regmap_mux prefix##_bit_clk = { \
+ .reg = _ns, \
+ .shift = _shift, \
+ .width = 1, \
+ .clkr = { \
+ .hw.init = &(struct clk_init_data){ \
+ .name = #prefix "_bit_clk", \
+ .parent_data = (const struct clk_parent_data[]){ \
+ { .hw = &prefix##_bit_div_clk.clkr.hw, }, \
+ { .fw_name = #prefix "_codec_clk", \
+ .name = #prefix "_codec_clk", }, \
+ }, \
+ .num_parents = 2, \
+ .ops = &clk_regmap_mux_closest_ops, \
+ .flags = CLK_SET_RATE_PARENT, \
+ }, \
+ }, \
+};
+
+CLK_AIF_OSR_SRC(mi2s, 0x48, 0x4c)
+CLK_AIF_OSR_CLK(mi2s, 0x48, 0x50, 17)
+CLK_AIF_OSR_DIV_CLK(mi2s, 0x48, 4)
+CLK_AIF_OSR_BIT_DIV_CLK(mi2s, 0x48, 0x50, 15)
+CLK_AIF_OSR_BIT_CLK(mi2s, 0x48, 14)
+
+/*
+ * CLK_AIF_OSR_DIV - Audio Interface with divider clocks.
+ *
+ * MSM8x60 LPASS AIF register layout, verified against the legacy
+ * downstream Samsung MSM8660 source and the webOS clock-8x60.c
+ * CLK_AIF_BIT macro. Both legacy sources agree on the bit assignment:
+ * the bit-divider field starts at offset 10 and is four bits wide;
+ * bit 14 doubles as the cdiv external-source select; BIT_DIV branch
+ * enable is BIT(15); OSR branch enable is BIT(17); the BIT clock mux
+ * select is BIT(18) for codec_i2s and spare_i2s (mi2s uses bit 14,
+ * handled explicitly above this macro); reset is BIT(19).
+ *
+ * Earlier revisions of this driver used a width of 8 here, inherited
+ * from lcc-msm8960.c whose enables are at BIT(19) and BIT(21) and so
+ * do not overlap a wide divider field. On MSM8x60 the enables are at
+ * BIT(15) and BIT(17), so a width of 8 made the divider field cover
+ * bits 10 through 17 and a read-modify-write on the divider would
+ * clobber the two branch gates. A width of 4 confines the divider to
+ * bits 10 through 13, matching the standalone mi2s div clock above
+ * and what the legacy stack programs.
+ */
+#define CLK_AIF_OSR_DIV(prefix, _ns, _md, hr) \
+ CLK_AIF_OSR_SRC(prefix, _ns, _md) \
+ CLK_AIF_OSR_CLK(prefix, _ns, hr, 17) \
+ CLK_AIF_OSR_DIV_CLK(prefix, _ns, 4) \
+ CLK_AIF_OSR_BIT_DIV_CLK(prefix, _ns, hr, 15) \
+ CLK_AIF_OSR_BIT_CLK(prefix, _ns, 18)
+
+CLK_AIF_OSR_DIV(codec_i2s_mic, 0x60, 0x64, 0x68);
+CLK_AIF_OSR_DIV(spare_i2s_mic, 0x78, 0x7c, 0x80);
+CLK_AIF_OSR_DIV(codec_i2s_spkr, 0x6c, 0x70, 0x74);
+CLK_AIF_OSR_DIV(spare_i2s_spkr, 0x84, 0x88, 0x8c);
+
+/* PCM frequency table for MSM8x60 with PLL4 at 540.672 MHz */
+static const struct freq_tbl clk_tbl_pcm_540[] = {
+ { 256000, P_PLL4, 4, 1, 528 },
+ { 512000, P_PLL4, 4, 1, 264 },
+ { 768000, P_PLL4, 4, 1, 176 },
+ { 1024000, P_PLL4, 4, 1, 132 },
+ { 1536000, P_PLL4, 4, 1, 88 },
+ { 2048000, P_PLL4, 4, 1, 66 },
+ { 3072000, P_PLL4, 4, 1, 44 },
+ { 4096000, P_PLL4, 4, 1, 33 },
+ { 6144000, P_PLL4, 4, 1, 22 },
+ { 8192000, P_PLL4, 2, 1, 33 },
+ { 12288000, P_PLL4, 4, 1, 11 },
+ { 24576000, P_PLL4, 2, 1, 11 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+static const struct freq_tbl clk_tbl_pcm_393[] = {
+ { 256000, P_PLL4, 4, 1, 384 },
+ { 512000, P_PLL4, 4, 1, 192 },
+ { 768000, P_PLL4, 4, 1, 128 },
+ { 1024000, P_PLL4, 4, 1, 96 },
+ { 1536000, P_PLL4, 4, 1, 64 },
+ { 2048000, P_PLL4, 4, 1, 48 },
+ { 3072000, P_PLL4, 4, 1, 32 },
+ { 4096000, P_PLL4, 4, 1, 24 },
+ { 6144000, P_PLL4, 4, 1, 16 },
+ { 8192000, P_PLL4, 4, 1, 12 },
+ { 12288000, P_PLL4, 4, 1, 8 },
+ { 24576000, P_PLL4, 4, 1, 4 },
+ { 27000000, P_PXO, 1, 0, 0 },
+ { }
+};
+
+static struct clk_rcg pcm_src = {
+ .ns_reg = 0x54,
+ .md_reg = 0x58,
+ .mn = {
+ .mnctr_en_bit = 8,
+ .mnctr_reset_bit = 7,
+ .mnctr_mode_shift = 5,
+ .n_val_shift = 16,
+ .m_val_shift = 16,
+ .width = 16,
+ },
+ .p = {
+ .pre_div_shift = 3,
+ .pre_div_width = 2,
+ },
+ .s = {
+ .src_sel_shift = 0,
+ .parent_map = lcc_pxo_pll4_map,
+ },
+ .freq_tbl = clk_tbl_pcm_393,
+ .clkr = {
+ .enable_reg = 0x54,
+ .enable_mask = BIT(9),
+ .hw.init = &(struct clk_init_data){
+ .name = "pcm_src",
+ .parent_data = lcc_pxo_pll4,
+ .num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+ .ops = &clk_rcg_ops,
+ .flags = CLK_SET_RATE_GATE,
+ },
+ },
+};
+
+static struct clk_branch pcm_clk_out = {
+ .halt_reg = 0x5c,
+ .halt_bit = 0,
+ .halt_check = BRANCH_HALT_ENABLE,
+ .clkr = {
+ .enable_reg = 0x54,
+ .enable_mask = BIT(11),
+ .hw.init = &(struct clk_init_data){
+ .name = "pcm_clk_out",
+ .parent_hws = (const struct clk_hw*[]){
+ &pcm_src.clkr.hw
+ },
+ .num_parents = 1,
+ .ops = &clk_branch_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_regmap_mux pcm_clk = {
+ .reg = 0x54,
+ .shift = 10,
+ .width = 1,
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "pcm_clk",
+ .parent_data = (const struct clk_parent_data[]){
+ { .hw = &pcm_clk_out.clkr.hw },
+ { .fw_name = "pcm_codec_clk", .name = "pcm_codec_clk" },
+ },
+ .num_parents = 2,
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_rcg slimbus_src = {
+ .ns_reg = 0xcc,
+ .md_reg = 0xd0,
+ .mn = {
+ .mnctr_en_bit = 8,
+ .mnctr_reset_bit = 7,
+ .mnctr_mode_shift = 5,
+ .n_val_shift = 24,
+ .m_val_shift = 8,
+ .width = 8,
+ },
+ .p = {
+ .pre_div_shift = 3,
+ .pre_div_width = 2,
+ },
+ .s = {
+ .src_sel_shift = 0,
+ .parent_map = lcc_pxo_pll4_map,
+ },
+ .freq_tbl = clk_tbl_aif_osr_393,
+ .clkr = {
+ .enable_reg = 0xcc,
+ .enable_mask = BIT(9),
+ .hw.init = &(struct clk_init_data){
+ .name = "slimbus_src",
+ .parent_data = lcc_pxo_pll4,
+ .num_parents = ARRAY_SIZE(lcc_pxo_pll4),
+ .ops = &clk_rcg_ops,
+ .flags = CLK_SET_RATE_GATE,
+ },
+ },
+};
+
+static struct clk_branch audio_slimbus_clk = {
+ .halt_reg = 0xd4,
+ .halt_bit = 0,
+ .halt_check = BRANCH_HALT_ENABLE,
+ .clkr = {
+ .enable_reg = 0xcc,
+ .enable_mask = BIT(10),
+ .hw.init = &(struct clk_init_data){
+ .name = "audio_slimbus_clk",
+ .parent_hws = (const struct clk_hw*[]){
+ &slimbus_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .ops = &clk_branch_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_branch sps_slimbus_clk = {
+ .halt_reg = 0xd4,
+ .halt_bit = 1,
+ .halt_check = BRANCH_HALT_ENABLE,
+ .clkr = {
+ .enable_reg = 0xcc,
+ .enable_mask = BIT(12),
+ .hw.init = &(struct clk_init_data){
+ .name = "sps_slimbus_clk",
+ .parent_hws = (const struct clk_hw*[]){
+ &slimbus_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .ops = &clk_branch_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+ },
+};
+
+static struct clk_regmap *lcc_msm8660_clks[] = {
+ [PLL4] = &pll4.clkr,
+ [MI2S_OSR_SRC] = &mi2s_osr_src.clkr,
+ [MI2S_OSR_CLK] = &mi2s_osr_clk.clkr,
+ [MI2S_DIV_CLK] = &mi2s_div_clk.clkr,
+ [MI2S_BIT_DIV_CLK] = &mi2s_bit_div_clk.clkr,
+ [MI2S_BIT_CLK] = &mi2s_bit_clk.clkr,
+ [PCM_SRC] = &pcm_src.clkr,
+ [PCM_CLK_OUT] = &pcm_clk_out.clkr,
+ [PCM_CLK] = &pcm_clk.clkr,
+ [SLIMBUS_SRC] = &slimbus_src.clkr,
+ [AUDIO_SLIMBUS_CLK] = &audio_slimbus_clk.clkr,
+ [SPS_SLIMBUS_CLK] = &sps_slimbus_clk.clkr,
+ [CODEC_I2S_MIC_OSR_SRC] = &codec_i2s_mic_osr_src.clkr,
+ [CODEC_I2S_MIC_OSR_CLK] = &codec_i2s_mic_osr_clk.clkr,
+ [CODEC_I2S_MIC_DIV_CLK] = &codec_i2s_mic_div_clk.clkr,
+ [CODEC_I2S_MIC_BIT_DIV_CLK] = &codec_i2s_mic_bit_div_clk.clkr,
+ [CODEC_I2S_MIC_BIT_CLK] = &codec_i2s_mic_bit_clk.clkr,
+ [SPARE_I2S_MIC_OSR_SRC] = &spare_i2s_mic_osr_src.clkr,
+ [SPARE_I2S_MIC_OSR_CLK] = &spare_i2s_mic_osr_clk.clkr,
+ [SPARE_I2S_MIC_DIV_CLK] = &spare_i2s_mic_div_clk.clkr,
+ [SPARE_I2S_MIC_BIT_DIV_CLK] = &spare_i2s_mic_bit_div_clk.clkr,
+ [SPARE_I2S_MIC_BIT_CLK] = &spare_i2s_mic_bit_clk.clkr,
+ [CODEC_I2S_SPKR_OSR_SRC] = &codec_i2s_spkr_osr_src.clkr,
+ [CODEC_I2S_SPKR_OSR_CLK] = &codec_i2s_spkr_osr_clk.clkr,
+ [CODEC_I2S_SPKR_DIV_CLK] = &codec_i2s_spkr_div_clk.clkr,
+ [CODEC_I2S_SPKR_BIT_DIV_CLK] = &codec_i2s_spkr_bit_div_clk.clkr,
+ [CODEC_I2S_SPKR_BIT_CLK] = &codec_i2s_spkr_bit_clk.clkr,
+ [SPARE_I2S_SPKR_OSR_SRC] = &spare_i2s_spkr_osr_src.clkr,
+ [SPARE_I2S_SPKR_OSR_CLK] = &spare_i2s_spkr_osr_clk.clkr,
+ [SPARE_I2S_SPKR_DIV_CLK] = &spare_i2s_spkr_div_clk.clkr,
+ [SPARE_I2S_SPKR_BIT_DIV_CLK] = &spare_i2s_spkr_bit_div_clk.clkr,
+ [SPARE_I2S_SPKR_BIT_CLK] = &spare_i2s_spkr_bit_clk.clkr,
+};
+
+static const struct regmap_config lcc_msm8660_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0xfc,
+ .fast_io = true,
+};
+
+static const struct qcom_cc_desc lcc_msm8660_desc = {
+ .config = &lcc_msm8660_regmap_config,
+ .clks = lcc_msm8660_clks,
+ .num_clks = ARRAY_SIZE(lcc_msm8660_clks),
+};
+
+static const struct of_device_id lcc_msm8660_match_table[] = {
+ { .compatible = "qcom,lcc-msm8260" },
+ { .compatible = "qcom,lcc-msm8660" },
+ { .compatible = "qcom,lcc-apq8060" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lcc_msm8660_match_table);
+
+static int lcc_msm8660_probe(struct platform_device *pdev)
+{
+ const struct freq_tbl *aif_osr_tbl, *pcm_tbl;
+ struct regmap *regmap;
+ const char *plan_name;
+ u32 val;
+ int ret;
+
+ regmap = qcom_cc_map(pdev, &lcc_msm8660_desc);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ /*
+ * MSM8x60 should always boot with PLL4 L=22 (540.672 MHz).
+ * Detect anyway so a board with a non-standard L value still gets a
+ * coherent frequency plan instead of silently producing wrong rates.
+ */
+ ret = regmap_read(regmap, 0x4, &val);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to read PLL4 L register\n");
+
+ if (val == 0x16) {
+ aif_osr_tbl = clk_tbl_aif_osr_540;
+ pcm_tbl = clk_tbl_pcm_540;
+ plan_name = "540MHz";
+ } else {
+ aif_osr_tbl = clk_tbl_aif_osr_393;
+ pcm_tbl = clk_tbl_pcm_393;
+ plan_name = "fallback 393MHz";
+ }
+
+ /*
+ * Pick the matching frequency table on both branches; assigning
+ * unconditionally also restores the default if this driver is
+ * ever rebound on a system whose PLL4 has been reprogrammed.
+ */
+ slimbus_src.freq_tbl = aif_osr_tbl;
+ mi2s_osr_src.freq_tbl = aif_osr_tbl;
+ codec_i2s_mic_osr_src.freq_tbl = aif_osr_tbl;
+ spare_i2s_mic_osr_src.freq_tbl = aif_osr_tbl;
+ codec_i2s_spkr_osr_src.freq_tbl = aif_osr_tbl;
+ spare_i2s_spkr_osr_src.freq_tbl = aif_osr_tbl;
+ pcm_src.freq_tbl = pcm_tbl;
+
+ dev_info(&pdev->dev, "PLL4 L=0x%x, using %s frequency plan\n",
+ val, plan_name);
+
+ /* Enable PLL4 source on the LPASS Primary PLL Mux */
+ regmap_write(regmap, 0xc4, 0x1);
+
+ return qcom_cc_really_probe(&pdev->dev, &lcc_msm8660_desc, regmap);
+}
+
+static struct platform_driver lcc_msm8660_driver = {
+ .probe = lcc_msm8660_probe,
+ .driver = {
+ .name = "lcc-msm8660",
+ .of_match_table = lcc_msm8660_match_table,
+ },
+};
+module_platform_driver(lcc_msm8660_driver);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 LPASS Clock Controller driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lcc-msm8660");
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-31 4:09 ` [PATCH v2 0/3] 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 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
` (4 subsequent siblings)
5 siblings, 0 replies; 46+ 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] 46+ messages in thread
* [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver 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; 46+ 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] 46+ messages in thread
* [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver 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:34 ` sashiko-bot
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, 1 reply; 46+ 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] 46+ messages in thread
* [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (13 preceding siblings ...)
2026-05-31 4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
15 siblings, 2 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Sebastian Andrzej Siewior,
Steven Rostedt, Thomas Gleixner, van Hazendonk
Hi all,
Self-review (with Sashiko AI assist) caught eight real 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:
- PREEMPT_RT: drop the syscon regmap and ioremap the vMPM sub-region
of the RPM control block directly. The IRQ core invokes the
irqchip mask/unmask/set_type/set_wake callbacks with the irq_desc
raw_spinlock_t held, and syscon regmaps use a sleepable
spinlock_t that becomes an rt_mutex on PREEMPT_RT. Direct MMIO
(readl_relaxed/writel_relaxed) is what every other SoC irqchip
in tree does for the same reason (qcom-pdc, gic-v3, ...).
- RPM doorbell: after every vMPM register write, send a message
through the qcom-apcs-ipc mailbox. v1 acquired the mailbox
channel but never used it, so the RPM kept running with its
cached enable / detect / polarity state and our configuration
changes had no effect.
- Teardown order: v1 used devm_request_irq() and then called
irq_domain_remove() manually in ->remove(); the devres-managed
handler outlives the domain, so an IRQ arriving in the removal
window would dereference a freed domain. Switch to plain
request_irq() / free_irq() and remove in strict reverse-init
order (free_irq, free mailbox, irq_domain_remove).
- .irq_set_wake: implement it (toggle the MPM enable bit + delegate
to irq_chip_set_wake_parent for the GIC) and drop
IRQCHIP_SKIP_SET_WAKE. v1 declared SKIP and provided no
callback, so enable_irq_wake() was a silent no-op for every
consumer.
- Clear-before-dispatch: in the IPC IRQ handler, write CLEAR for
the pending bits before calling generic_handle_domain_irq() for
each pin. v1 cleared after the handler returned, racing against
a fresh edge latched during dispatch and silently dropping it.
- DT pin-map bounds check: validate each qcom,mpm-pin-map pin
entry against MSM8660_MPM_PIN_COUNT before storing it, so a
malformed DT cannot induce out-of-bounds register accesses in
later set_type/enable paths.
- Consumer API lifetime: msm8660_mpm_get() now takes a consumer
device argument and establishes a DL_FLAG_AUTOREMOVE_CONSUMER
device link to the MPM device. Without this, an unbind of the
MPM driver would free the devres-managed struct while consumers
still hold pointers, causing UAF on the next call.
- regmap_read return check: moot now that we use readl_relaxed,
but the original Sashiko finding (uninitialised val on regmap
error) is closed.
DT binding: drop qcom,rpm-syscon and qcom,mpm-offset; replace with a
single reg property pointing at the vMPM window inside the RPM
control block. dt_binding_check passes on the example.
Driver passes checkpatch with zero warnings or errors.
Thanks,
Herman
Herman van Hazendonk (2):
dt-bindings: interrupt-controller: qcom: add msm8660-mpm
irqchip: add MSM8x60 MPM wakeup interrupt controller driver
.../qcom,msm8660-mpm.yaml | 122 +++
drivers/irqchip/Kconfig | 23 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-msm8660-mpm.c | 740 ++++++++++++++++++
include/soc/qcom/msm8660-mpm.h | 83 ++
5 files changed, 969 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
create mode 100644 drivers/irqchip/irq-msm8660-mpm.c
create mode 100644 include/soc/qcom/msm8660-mpm.h
--
2.43.0
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:20 ` sashiko-bot
2026-05-31 8:01 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
1 sibling, 2 replies; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Sebastian Andrzej Siewior,
Steven Rostedt, Thomas Gleixner, van Hazendonk
Add the binding for the Modem Power Manager (MPM) interrupt
controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The MPM
is a small wake-capable interrupt aggregator that lets the SoC stay
in low-power states while a small set of GIC SPIs continues to be
monitored and can wake the system on an edge.
The binding describes its register region (in the RPM syscon), the
GIC SPI it raises on a wake event, the GIC mailbox it shares with
the RPM, and the per-pin wake-source mapping table.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../qcom,msm8660-mpm.yaml | 122 ++++++++++++++++++
1 file changed, 122 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
new file mode 100644
index 000000000000..1288e83d75d6
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
@@ -0,0 +1,122 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/qcom,msm8660-mpm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm MSM8x60 MPM wakeup interrupt controller
+
+maintainers:
+ - Herman van Hazendonk <github.com@herrie.org>
+
+description: |
+ The MSM Power Manager (MPM) on the MSM8x60 family (MSM8260/MSM8660/
+ APQ8060) is an always-on hardware block that keeps a set of wake
+ interrupts alive while the application processor is powered down.
+
+ The vMPM (virtual MPM) registers live INSIDE the RPM control block:
+ the request window (ENABLE, DETECT_CTL, POLARITY, CLEAR) starts at
+ RPM_BASE + 0x9d8 and the status window starts 0x420 above the
+ request window. The reg property points at this vMPM sub-region;
+ the qcom,rpm-msm8660 driver maps the surrounding RPM control area
+ for its own use, so the two mappings overlap but neither claims
+ exclusive ownership of the other's range.
+
+ Notification of new wake-source configuration is delivered via the
+ qcom-apcs-ipc mailbox (writing GCC + 0x008 bit 1).
+
+ This binding is MSM8x60-specific. Newer Qualcomm SoCs (sm6375, etc.)
+ use the qcom,mpm binding which assumes a dedicated MPM SRAM region
+ and an IPCC mailbox - neither holds on MSM8x60. The driver lives at
+ drivers/irqchip/irq-msm8660-mpm.c.
+
+allOf:
+ - $ref: /schemas/interrupt-controller.yaml#
+
+properties:
+ compatible:
+ const: qcom,msm8660-mpm
+
+ reg:
+ maxItems: 1
+ description: |
+ vMPM register window inside the RPM control block. Must cover
+ the request registers at offset 0 of the window and the status
+ registers at offset 0x420 of the window. A length of 0x440 is
+ sufficient for the four-word request banks and the status bank.
+
+ interrupts:
+ maxItems: 1
+ description:
+ IPC IRQ raised by MPM when one of the enabled wake sources fires.
+ On MSM8x60 this is wired to GIC SPI 2.
+
+ mboxes:
+ maxItems: 1
+ description:
+ Mailbox channel used to notify MPM that the vMPM request
+ registers have been updated. On MSM8x60 this is the
+ qcom-apcs-ipc mailbox channel 1 (writes GCC + 0x008 bit 1).
+
+ interrupt-controller: true
+
+ '#interrupt-cells':
+ const: 2
+ description:
+ First cell is the MPM pin number / parent GIC SPI; second cell
+ is the trigger type.
+
+ qcom,mpm-pin-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Total number of MPM pins exposed.
+
+ qcom,mpm-pin-map:
+ $ref: /schemas/types.yaml#/definitions/uint32-matrix
+ items:
+ items:
+ - description: MPM pin number
+ - description: GIC SPI number this pin maps to
+ description: |
+ List of (MPM-pin, GIC-SPI) tuples for wake sources that have a
+ corresponding GIC IRQ. Consumers route their interrupts through
+ the MPM hierarchical irqdomain to take advantage of these
+ mappings.
+
+ Raw wake pins like SDC3/4 DATx (pins 21-24) are NOT listed
+ here - they have no GIC SPI mapping and are accessed through
+ the msm8660_mpm_set_pin_wake() / msm8660_mpm_enable_pin() C API
+ by consumer drivers.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - mboxes
+ - interrupt-controller
+ - '#interrupt-cells'
+ - qcom,mpm-pin-count
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ soc {
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ msm8660_mpm: interrupt-controller@1049d8 {
+ compatible = "qcom,msm8660-mpm";
+ reg = <0x001049d8 0x440>;
+ interrupts = <GIC_SPI 2 IRQ_TYPE_EDGE_RISING>;
+ mboxes = <&gcc_ipc 1>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ interrupt-parent = <&intc>;
+ qcom,mpm-pin-count = <64>;
+ qcom,mpm-pin-map = <25 100>,
+ <26 50>,
+ <27 79>;
+ };
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
@ 2026-05-31 4:09 ` Herman van Hazendonk
2026-05-31 4:32 ` sashiko-bot
1 sibling, 1 reply; 46+ messages in thread
From: Herman van Hazendonk @ 2026-05-31 4:09 UTC (permalink / raw)
To: Bjorn Andersson, Clark Williams, Conor Dooley, devicetree,
Konrad Dybcio, Krzysztof Kozlowski, linux-arm-msm, linux-kernel,
linux-rt-devel, Rob Herring, Sebastian Andrzej Siewior,
Steven Rostedt, Thomas Gleixner, van Hazendonk
Add a driver for the MSM Power Manager (MPM) on the MSM8x60 family
(MSM8260/MSM8660/APQ8060). The MPM is a small wake-source controller
implemented in the always-on power domain that latches edge-triggered
interrupts during APPS power collapse and signals the RPM to wake the
APPS back up.
The driver:
- implements an irqchip that wraps a parent GIC SPI line and
overrides ->irq_set_wake() to enable the MPM mirror of the same
interrupt;
- exposes msm8660_mpm_set_pin_wake() etc. for consumers (e.g.
mmci's SDC4 SDIO wake) that do not flow through irq_set_wake()
directly;
- communicates with the RPM via SMSM/syscon to apply the wake
enable/clear bits and request a power collapse vote.
Used on the HP TouchPad (Tenderloin) for SDIO-attached Wi-Fi and
panel touch wake-from-suspend.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/irqchip/Kconfig | 23 +
drivers/irqchip/Makefile | 1 +
drivers/irqchip/irq-msm8660-mpm.c | 740 ++++++++++++++++++++++++++++++
include/soc/qcom/msm8660-mpm.h | 83 ++++
4 files changed, 847 insertions(+)
create mode 100644 drivers/irqchip/irq-msm8660-mpm.c
create mode 100644 include/soc/qcom/msm8660-mpm.h
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index e755a2a05209..35598a56ac79 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -525,6 +525,29 @@ config QCOM_MPM
MSM Power Manager driver to manage and configure wakeup
IRQs for Qualcomm Technologies Inc (QTI) mobile chips.
+config QCOM_MSM8660_MPM
+ bool "MSM8x60 MPM wakeup interrupt controller"
+ depends on ARCH_QCOM
+ depends on MFD_SYSCON
+ depends on MAILBOX
+ select IRQ_DOMAIN_HIERARCHY
+ default y
+ help
+ Platform driver for the MSM Power Manager (MPM) wakeup interrupt
+ controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The vMPM registers live inside
+ the RPM control block, which makes the generic QCOM_MPM driver
+ unusable here (it assumes a dedicated MPM SRAM region, an IPCC
+ mailbox, and uses IRQCHIP_DECLARE early-init that races platform
+ device creation).
+
+ This driver replicates the legacy 2.6.35-palm arch/arm/mach-msm/
+ mpm.c mechanism as a regular platform driver, accessing the vMPM
+ registers via a syscon phandle to the RPM block and signaling the
+ MPM via the qcom-apcs-ipc mailbox (writing to GCC + 0x008 bit 1).
+
+ Required for cpuidle deep-sleep states (SPC/PC) and for
+ WoWLAN-style wake-from-suspend on MSM8x60 SoCs.
+
config CSKY_MPINTC
bool
depends on CSKY
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 26aa3b6ec99f..3e64591f0f5b 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_MESON_IRQ_GPIO) += irq-meson-gpio.o
obj-$(CONFIG_GOLDFISH_PIC) += irq-goldfish-pic.o
obj-$(CONFIG_QCOM_PDC) += qcom-pdc.o
obj-$(CONFIG_QCOM_MPM) += irq-qcom-mpm.o
+obj-$(CONFIG_QCOM_MSM8660_MPM) += irq-msm8660-mpm.o
obj-$(CONFIG_CSKY_MPINTC) += irq-csky-mpintc.o
obj-$(CONFIG_CSKY_APB_INTC) += irq-csky-apb-intc.o
obj-$(CONFIG_RISCV_INTC) += irq-riscv-intc.o
diff --git a/drivers/irqchip/irq-msm8660-mpm.c b/drivers/irqchip/irq-msm8660-mpm.c
new file mode 100644
index 000000000000..b8394d9c076d
--- /dev/null
+++ b/drivers/irqchip/irq-msm8660-mpm.c
@@ -0,0 +1,740 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM (MSM Power Manager) wakeup interrupt controller
+ *
+ * The MPM is an always-on hardware block that keeps a small set of wake
+ * sources alive while the application processor is powered down for
+ * cpuidle Power Collapse or suspend-to-RAM. On MSM8x60 the
+ * vMPM (virtual MPM) registers live INSIDE the RPM's 4 KB control block
+ * at:
+ *
+ * request (control) regs: RPM_BASE + 0x9d8 (ENABLE, DETECT_CTL,
+ * POLARITY, CLEAR)
+ * status (pending) regs: RPM_BASE + 0xdf8 (== 0x9d8 + 0x420)
+ *
+ * The mainline qcom-mpm driver (drivers/irqchip/irq-qcom-mpm.c) is
+ * fundamentally incompatible with this layout:
+ * - it assumes a dedicated MPM SRAM region separate from RPM;
+ * - it assumes a mailbox controller (IPCC) for wake notification;
+ * - it uses IRQCHIP_DECLARE which runs before platform devices exist,
+ * so of_find_device_by_node() returns NULL and the init silently
+ * hangs.
+ *
+ * This driver replicates the 2.6.35-palm `arch/arm/mach-msm/mpm.c`
+ * mechanism as a regular platform driver: probes after platform
+ * infrastructure is ready, ioremaps the vMPM sub-region of the RPM
+ * control block (the qcom,rpm driver maps the surrounding area for
+ * its own use; the two mappings overlap and neither claims exclusive
+ * ownership), and uses the qcom-apcs-ipc mailbox for wake notification
+ * (writing to GCC + 0x008 bit 1).
+ *
+ * Register access is done with readl_relaxed/writel_relaxed rather than
+ * via the RPM syscon regmap. The IRQ core invokes our mask/unmask/
+ * set_type/set_wake callbacks with the irq_desc's raw_spinlock_t held,
+ * and syscon regmaps use a sleepable spinlock_t which on PREEMPT_RT
+ * would deadlock under that raw lock. Direct MMIO is also what every
+ * other SoC irqchip (qcom-pdc, gic-v3, ...) does.
+ *
+ * Two consumer interfaces:
+ *
+ * 1. Hierarchical irqdomain: for MPM pins that map to GIC SPIs (USB,
+ * HDMI, ...). Consumers wire their interrupts through this
+ * controller via interrupts-extended and the kernel manages
+ * enable / mask / set_type / set_wake via the IRQ subsystem.
+ *
+ * 2. Raw-pin API: for MPM pins that do NOT correspond to a GIC IRQ
+ * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24).
+ * These are physical wake-signal lines monitored by MPM
+ * directly. Consumers (mmci for SDC4 wake) call
+ * msm8660_mpm_set_pin_wake() etc. The consumer API establishes
+ * a device_link from consumer to producer so the MPM device
+ * cannot disappear while a consumer holds a handle.
+ *
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ * Copyright (c) 2010-2012, The Linux Foundation (legacy mpm.c reference)
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <soc/qcom/msm8660-mpm.h>
+
+/*
+ * vMPM register offsets (relative to the start of the ioremap'd window
+ * = RPM base + 0x9d8). Each register is two 32-bit slots because MPM
+ * exposes 64 wake pins.
+ */
+#define MSM8660_MPM_REG_ENABLE 0x00
+#define MSM8660_MPM_REG_DETECT_CTL 0x08
+#define MSM8660_MPM_REG_POLARITY 0x10
+#define MSM8660_MPM_REG_CLEAR 0x18
+
+/* Status registers at +0x420 from vMPM base (== RPM + 0xdf8). */
+#define MSM8660_MPM_STATUS_OFFSET 0x420
+
+#define MSM8660_MPM_PIN_COUNT 64
+#define MSM8660_MPM_REG_WIDTH 2
+
+struct msm8660_mpm_pin {
+ int pin;
+ int hwirq;
+};
+
+struct msm8660_mpm {
+ struct device *dev;
+ void __iomem *base;
+ struct irq_domain *domain;
+ struct msm8660_mpm_pin *pin_map;
+ unsigned int pin_map_count;
+ int parent_irq;
+ struct mbox_client mbox_client;
+ struct mbox_chan *mbox_chan;
+};
+
+/*
+ * Singleton - there is only one MPM instance per SoC. msm8660_mpm_get()
+ * returns this. Updates are serialised through the binding lifecycle so
+ * a plain pointer is sufficient.
+ */
+static struct msm8660_mpm *msm8660_mpm_global;
+
+static u32 msm8660_mpm_read(struct msm8660_mpm *mpm, unsigned int reg)
+{
+ return readl_relaxed(mpm->base + reg);
+}
+
+static void msm8660_mpm_write(struct msm8660_mpm *mpm, unsigned int reg,
+ u32 val)
+{
+ writel_relaxed(val, mpm->base + reg);
+}
+
+/*
+ * Doorbell the RPM after touching the vMPM request registers. Without
+ * this the RPM keeps using its last cached copy of the enable/detect/
+ * polarity state and our configuration changes have no effect.
+ *
+ * Called from raw_spinlock_t-held contexts (irq_chip mask/unmask/
+ * set_type/set_wake), so the mailbox driver must accept that. The
+ * qcom-apcs-ipc mailbox just does a writel into the IPC trigger
+ * register; it is safe under a raw lock.
+ */
+static void msm8660_mpm_doorbell(struct msm8660_mpm *mpm)
+{
+ int ret;
+
+ if (!mpm->mbox_chan)
+ return;
+
+ ret = mbox_send_message(mpm->mbox_chan, NULL);
+ if (ret < 0)
+ dev_warn_ratelimited(mpm->dev,
+ "RPM doorbell failed: %d\n", ret);
+}
+
+static int msm8660_mpm_pin_to_hwirq(struct msm8660_mpm *mpm, int pin)
+{
+ int i;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].pin == pin)
+ return mpm->pin_map[i].hwirq;
+ }
+ return -ENOENT;
+}
+
+/*
+ * IPC handler: MPM fires this IRQ when one or more enabled wake pins
+ * have pending activity. Read pending status, CLEAR the pending bits
+ * BEFORE dispatching the per-pin handlers so a fresh edge that arrives
+ * during dispatch cannot be wiped out by a later CLEAR write, then
+ * replay each pending pin through the irqdomain.
+ */
+static irqreturn_t msm8660_mpm_irq(int irq, void *data)
+{
+ struct msm8660_mpm *mpm = data;
+ unsigned long pending[MSM8660_MPM_REG_WIDTH];
+ unsigned long enable[MSM8660_MPM_REG_WIDTH];
+ int i, j;
+
+ for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
+ pending[i] = msm8660_mpm_read(mpm,
+ MSM8660_MPM_STATUS_OFFSET + i * 4);
+ enable[i] = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + i * 4);
+ pending[i] &= enable[i];
+
+ /*
+ * Clear before dispatching: a new edge latched on this pin
+ * after this point will set the pending bit again and we
+ * will service it on the next IPC IRQ. Clearing AFTER the
+ * handler would race with that new latch and silently lose
+ * the new edge.
+ */
+ if (pending[i])
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_CLEAR + i * 4, pending[i]);
+ }
+
+ for (i = 0; i < MSM8660_MPM_REG_WIDTH; i++) {
+ unsigned long bits = pending[i];
+
+ for_each_set_bit(j, &bits, 32) {
+ int pin = i * 32 + j;
+ int hwirq = msm8660_mpm_pin_to_hwirq(mpm, pin);
+
+ if (hwirq >= 0) {
+ dev_dbg(mpm->dev, "wake pin %d -> hwirq %d\n",
+ pin, hwirq);
+ generic_handle_domain_irq(mpm->domain, hwirq);
+ }
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Resolve an irq_data hwirq back to its MPM pin number, or -1 if the
+ * pin is not in the qcom,mpm-pin-map (which would mean a consumer
+ * bound to a hwirq that has no MPM mapping).
+ */
+static int msm8660_mpm_hwirq_to_pin(struct msm8660_mpm *mpm, unsigned int hwirq)
+{
+ int i;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ if (mpm->pin_map[i].hwirq == hwirq)
+ return mpm->pin_map[i].pin;
+ }
+ return -1;
+}
+
+static void msm8660_mpm_enable_hwirq(struct irq_data *d, bool enable)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ int pin;
+ u32 val, mask;
+
+ pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
+ if (pin < 0)
+ return;
+
+ mask = BIT(pin % 32);
+ val = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
+ if (enable)
+ val |= mask;
+ else
+ val &= ~mask;
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
+
+ msm8660_mpm_doorbell(mpm);
+}
+
+static void msm8660_mpm_mask_irq(struct irq_data *d)
+{
+ msm8660_mpm_enable_hwirq(d, false);
+ irq_chip_mask_parent(d);
+}
+
+static void msm8660_mpm_unmask_irq(struct irq_data *d)
+{
+ msm8660_mpm_enable_hwirq(d, true);
+ irq_chip_unmask_parent(d);
+}
+
+static int msm8660_mpm_set_type(struct irq_data *d, unsigned int type)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ u32 detect, polarity, mask;
+ int pin;
+
+ pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
+ if (pin < 0)
+ return -ENOENT;
+
+ mask = BIT(pin % 32);
+ detect = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
+ polarity = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4);
+
+ switch (type) {
+ case IRQ_TYPE_EDGE_RISING:
+ detect |= mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ detect |= mask;
+ polarity &= ~mask;
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ detect &= ~mask;
+ polarity |= mask;
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ detect &= ~mask;
+ polarity &= ~mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4, detect);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4, polarity);
+
+ msm8660_mpm_doorbell(mpm);
+
+ return irq_chip_set_type_parent(d, type);
+}
+
+/*
+ * Toggle MPM monitoring of the pin and propagate the wake request to
+ * the parent GIC so it also stays alive during power-collapse.
+ *
+ * Without this callback the generic IRQ core would either silently
+ * succeed (with IRQCHIP_SKIP_SET_WAKE) or fail outright, and neither
+ * the MPM nor the GIC would actually be programmed to wake the system.
+ */
+static int msm8660_mpm_set_wake(struct irq_data *d, unsigned int on)
+{
+ struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
+ int pin;
+
+ pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
+ if (pin < 0)
+ return -ENOENT;
+
+ msm8660_mpm_enable_hwirq(d, !!on);
+
+ return irq_chip_set_wake_parent(d, on);
+}
+
+static struct irq_chip msm8660_mpm_chip = {
+ .name = "msm8660-mpm",
+ .irq_mask = msm8660_mpm_mask_irq,
+ .irq_unmask = msm8660_mpm_unmask_irq,
+ .irq_set_type = msm8660_mpm_set_type,
+ .irq_set_wake = msm8660_mpm_set_wake,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .flags = IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static int msm8660_mpm_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *data)
+{
+ struct msm8660_mpm *mpm = domain->host_data;
+ struct irq_fwspec *fwspec = data;
+ struct irq_fwspec parent_fwspec;
+ irq_hw_number_t hwirq;
+ int i, ret;
+
+ if (fwspec->param_count != 2)
+ return -EINVAL;
+
+ hwirq = fwspec->param[0];
+
+ for (i = 0; i < nr_irqs; i++)
+ irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+ &msm8660_mpm_chip, mpm);
+
+ parent_fwspec.fwnode = domain->parent->fwnode;
+ parent_fwspec.param_count = 3;
+ parent_fwspec.param[0] = 0;
+ parent_fwspec.param[1] = hwirq;
+ parent_fwspec.param[2] = fwspec->param[1];
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
+ &parent_fwspec);
+ if (ret) {
+ dev_err(mpm->dev, "irq_domain_alloc_irqs_parent failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int msm8660_mpm_translate(struct irq_domain *d,
+ struct irq_fwspec *fwspec,
+ unsigned long *hwirq, unsigned int *type)
+{
+ if (fwspec->param_count != 2)
+ return -EINVAL;
+
+ *hwirq = fwspec->param[0];
+ *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
+ return 0;
+}
+
+static const struct irq_domain_ops msm8660_mpm_domain_ops = {
+ .translate = msm8660_mpm_translate,
+ .alloc = msm8660_mpm_domain_alloc,
+ .free = irq_domain_free_irqs_common,
+};
+
+/* ===================================================================
+ * Raw-pin consumer API
+ * ===================================================================
+ */
+
+/**
+ * msm8660_mpm_get() - acquire a handle to the MPM for raw-pin use
+ * @consumer: device of the consumer driver
+ * @np: optional device-tree node containing a phandle reference
+ * @propname: optional property name for that phandle (e.g. "qcom,mpm")
+ *
+ * Returns the singleton MPM handle, ERR_PTR(-EPROBE_DEFER) if the MPM
+ * driver has not finished probing yet, or ERR_PTR(-ENOENT) if @np is
+ * given and the phandle does not resolve.
+ *
+ * On success this also establishes a consumer-supplier device_link so
+ * the MPM device cannot be unbound while the consumer holds the
+ * handle. The link is auto-removed when @consumer is unbound.
+ */
+struct msm8660_mpm *msm8660_mpm_get(struct device *consumer,
+ struct device_node *np,
+ const char *propname)
+{
+ struct device_node *mpm_np;
+ struct device_link *link;
+
+ if (!msm8660_mpm_global)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (np && propname) {
+ mpm_np = of_parse_phandle(np, propname, 0);
+ if (!mpm_np)
+ return ERR_PTR(-ENOENT);
+ of_node_put(mpm_np);
+ }
+
+ if (!consumer)
+ return msm8660_mpm_global;
+
+ link = device_link_add(consumer, msm8660_mpm_global->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ if (!link) {
+ dev_warn(consumer, "failed to link to MPM, deferring\n");
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ return msm8660_mpm_global;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_get);
+
+/**
+ * msm8660_mpm_enable_pin() - enable/disable MPM monitoring of a pin
+ * @mpm: handle from msm8660_mpm_get()
+ * @pin: MPM pin index (0..MSM8660_MPM_PIN_COUNT-1)
+ * @enable: true to monitor, false to ignore
+ *
+ * Programs the ENABLE register directly. Intended for "raw" wake pins
+ * (SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23, SDC4_DAT3=24) that have no
+ * GIC IRQ mapping. For pins that DO have a GIC mapping (in
+ * qcom,mpm-pin-map), use the irqdomain path instead.
+ */
+int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
+ bool enable)
+{
+ u32 val;
+
+ if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
+ return -EINVAL;
+
+ val = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
+ if (enable)
+ val |= BIT(pin % 32);
+ else
+ val &= ~BIT(pin % 32);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
+
+ msm8660_mpm_doorbell(mpm);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_enable_pin);
+
+/**
+ * msm8660_mpm_set_pin_wake() - mark a pin as wake-capable
+ * @mpm: handle
+ * @pin: MPM pin index
+ * @on: true to allow this pin to wake the system, false to clear.
+ *
+ * Equivalent to msm8660_mpm_enable_pin() on MSM8660 - the hardware has
+ * a single ENABLE register, not separate enable + wake masks. The name
+ * is kept for API parity with the legacy mpm.h interface so consumers
+ * can express the wake-source intent explicitly.
+ */
+int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm, unsigned int pin,
+ bool on)
+{
+ return msm8660_mpm_enable_pin(mpm, pin, on);
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_set_pin_wake);
+
+/**
+ * msm8660_mpm_set_pin_type() - set trigger type for a raw MPM pin
+ * @mpm: handle
+ * @pin: MPM pin index
+ * @flow_type: standard IRQ_TYPE_* constants
+ *
+ * On MSM8660 the trigger config is split across DETECT_CTL (edge vs
+ * level) and POLARITY (rising/high vs falling/low).
+ */
+int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm, unsigned int pin,
+ unsigned int flow_type)
+{
+ u32 detect, polarity;
+ bool edge, polarity_high;
+
+ if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
+ return -EINVAL;
+
+ edge = !!(flow_type & IRQ_TYPE_EDGE_BOTH);
+ polarity_high = !!(flow_type & (IRQ_TYPE_EDGE_RISING |
+ IRQ_TYPE_LEVEL_HIGH));
+
+ detect = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
+ polarity = msm8660_mpm_read(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4);
+
+ if (edge)
+ detect |= BIT(pin % 32);
+ else
+ detect &= ~BIT(pin % 32);
+
+ if (polarity_high)
+ polarity |= BIT(pin % 32);
+ else
+ polarity &= ~BIT(pin % 32);
+
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4, detect);
+ msm8660_mpm_write(mpm,
+ MSM8660_MPM_REG_POLARITY + (pin / 32) * 4, polarity);
+
+ msm8660_mpm_doorbell(mpm);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(msm8660_mpm_set_pin_type);
+
+/* ===================================================================
+ * Platform driver
+ * ===================================================================
+ */
+
+static int msm8660_mpm_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct msm8660_mpm *mpm;
+ struct irq_domain *parent_domain;
+ struct device_node *parent_np;
+ struct resource *res;
+ int ret, i;
+
+ if (msm8660_mpm_global)
+ return dev_err_probe(dev, -EBUSY,
+ "only one MPM instance is supported\n");
+
+ mpm = devm_kzalloc(dev, sizeof(*mpm), GFP_KERNEL);
+ if (!mpm)
+ return -ENOMEM;
+
+ mpm->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return dev_err_probe(dev, -ENODEV, "missing reg property\n");
+
+ /*
+ * Use a non-exclusive mapping: the qcom,rpm driver maps the
+ * surrounding RPM control block via its own platform resource,
+ * and our vMPM sub-region overlaps that mapping. devm_ioremap()
+ * does not call request_mem_region() so there is no conflict.
+ */
+ mpm->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!mpm->base)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to ioremap vMPM at %pR\n", res);
+
+ /*
+ * Parse pin map (IRQ-mapped wake pins; raw pins like SDC4_DAT1
+ * are not listed here - they are accessed via the pin-API).
+ */
+ ret = of_property_count_u32_elems(np, "qcom,mpm-pin-map");
+ if (ret < 0 || ret % 2)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid qcom,mpm-pin-map\n");
+
+ mpm->pin_map_count = ret / 2;
+ mpm->pin_map = devm_kcalloc(dev, mpm->pin_map_count,
+ sizeof(*mpm->pin_map), GFP_KERNEL);
+ if (!mpm->pin_map)
+ return -ENOMEM;
+
+ for (i = 0; i < mpm->pin_map_count; i++) {
+ u32 pin, hwirq;
+
+ of_property_read_u32_index(np, "qcom,mpm-pin-map",
+ i * 2, &pin);
+ of_property_read_u32_index(np, "qcom,mpm-pin-map",
+ i * 2 + 1, &hwirq);
+
+ if (pin >= MSM8660_MPM_PIN_COUNT)
+ return dev_err_probe(dev, -EINVAL,
+ "qcom,mpm-pin-map entry %d: pin %u >= %u\n",
+ i, pin, MSM8660_MPM_PIN_COUNT);
+
+ mpm->pin_map[i].pin = pin;
+ mpm->pin_map[i].hwirq = hwirq;
+
+ dev_dbg(dev, "pin map: pin %u -> hwirq %u\n", pin, hwirq);
+ }
+
+ parent_np = of_irq_find_parent(np);
+ if (!parent_np)
+ return dev_err_probe(dev, -ENODEV,
+ "failed to find parent interrupt controller\n");
+
+ parent_domain = irq_find_host(parent_np);
+ of_node_put(parent_np);
+ if (!parent_domain)
+ return dev_err_probe(dev, -ENODEV,
+ "failed to find parent IRQ domain\n");
+
+ mpm->domain = irq_domain_create_hierarchy(parent_domain, 0,
+ MSM8660_MPM_PIN_COUNT,
+ of_fwnode_handle(np),
+ &msm8660_mpm_domain_ops,
+ mpm);
+ if (!mpm->domain)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to create IRQ domain\n");
+
+ mpm->parent_irq = platform_get_irq(pdev, 0);
+ if (mpm->parent_irq < 0) {
+ ret = mpm->parent_irq;
+ goto err_remove_domain;
+ }
+
+ /*
+ * Mailbox channel for poking MPM to re-read its config. Get this
+ * BEFORE registering our IRQ handler so that doorbell-from-IRQ
+ * never sees a partially-initialised channel pointer.
+ */
+ mpm->mbox_client.dev = dev;
+ mpm->mbox_client.knows_txdone = true;
+ mpm->mbox_chan = mbox_request_channel(&mpm->mbox_client, 0);
+ if (IS_ERR(mpm->mbox_chan)) {
+ ret = PTR_ERR(mpm->mbox_chan);
+ mpm->mbox_chan = NULL;
+ if (ret == -EPROBE_DEFER)
+ goto err_remove_domain;
+ dev_warn(dev, "no mailbox channel: %d (continuing without RPM doorbell)\n",
+ ret);
+ }
+
+ /*
+ * Register the parent IRQ last and use plain request_irq() (not
+ * devm_*) so we can free it explicitly in ->remove() before
+ * irq_domain_remove(). With devm_request_irq() the handler
+ * outlives irq_domain_remove() and a wake event arriving in the
+ * removal window would dereference a freed domain pointer.
+ */
+ ret = request_irq(mpm->parent_irq, msm8660_mpm_irq,
+ IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND,
+ "msm8660-mpm", mpm);
+ if (ret) {
+ dev_err(dev, "failed to request IRQ %d: %d\n",
+ mpm->parent_irq, ret);
+ goto err_free_mbox;
+ }
+
+ platform_set_drvdata(pdev, mpm);
+ msm8660_mpm_global = mpm;
+
+ dev_info(dev, "ready: %d pin mappings, irq=%d\n",
+ mpm->pin_map_count, mpm->parent_irq);
+
+ return 0;
+
+err_free_mbox:
+ if (mpm->mbox_chan)
+ mbox_free_channel(mpm->mbox_chan);
+err_remove_domain:
+ irq_domain_remove(mpm->domain);
+ return ret;
+}
+
+static void msm8660_mpm_remove(struct platform_device *pdev)
+{
+ struct msm8660_mpm *mpm = platform_get_drvdata(pdev);
+
+ /*
+ * Tear down in strict reverse order: drop the singleton so new
+ * consumers cannot grab a handle, free the IRQ so the handler
+ * cannot fire again, free the mailbox channel, then remove the
+ * domain. Consumer device_links established in msm8660_mpm_get()
+ * prevent the parent device from being unbound while a consumer
+ * still holds a handle.
+ */
+ msm8660_mpm_global = NULL;
+ free_irq(mpm->parent_irq, mpm);
+ if (mpm->mbox_chan)
+ mbox_free_channel(mpm->mbox_chan);
+ irq_domain_remove(mpm->domain);
+}
+
+static const struct of_device_id msm8660_mpm_of_match[] = {
+ { .compatible = "qcom,msm8660-mpm" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, msm8660_mpm_of_match);
+
+static struct platform_driver msm8660_mpm_driver = {
+ .probe = msm8660_mpm_probe,
+ .remove = msm8660_mpm_remove,
+ .driver = {
+ .name = "msm8660-mpm",
+ .of_match_table = msm8660_mpm_of_match,
+ },
+};
+
+static int __init msm8660_mpm_init(void)
+{
+ return platform_driver_register(&msm8660_mpm_driver);
+}
+subsys_initcall(msm8660_mpm_init);
+
+static void __exit msm8660_mpm_exit(void)
+{
+ platform_driver_unregister(&msm8660_mpm_driver);
+}
+module_exit(msm8660_mpm_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 MPM wakeup interrupt controller");
+MODULE_LICENSE("GPL");
diff --git a/include/soc/qcom/msm8660-mpm.h b/include/soc/qcom/msm8660-mpm.h
new file mode 100644
index 000000000000..27f3f22d57b4
--- /dev/null
+++ b/include/soc/qcom/msm8660-mpm.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) MPM wake-source consumer interface.
+ *
+ * The MPM driver lives at drivers/irqchip/irq-msm8660-mpm.c. It exposes
+ * TWO interfaces:
+ *
+ * 1. Hierarchical irqdomain (preferred). For wake sources that map to
+ * GIC SPIs (USB1_HS, HDMI, ...). Consumers wire their IRQ through
+ * the MPM via `interrupts-extended = <&msm8660_mpm ...>` in DT and
+ * the IRQ subsystem manages enable / mask / set_type via the
+ * irqdomain alloc path. No explicit C API call needed.
+ *
+ * 2. Raw-pin API (this header). For wake sources that do NOT have a
+ * GIC IRQ mapping: SDC3_DAT1=21, SDC3_DAT3=22, SDC4_DAT1=23,
+ * SDC4_DAT3=24. These are physical wake-signal lines that MPM
+ * monitors directly. mmci (for SDC4 WiFi wake) obtains a handle
+ * via msm8660_mpm_get() and uses the helpers below.
+ */
+
+#ifndef __SOC_QCOM_MSM8660_MPM_H__
+#define __SOC_QCOM_MSM8660_MPM_H__
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/irq.h>
+
+#define MSM8660_MPM_NR_PINS 64
+
+/* Well-known wake-source pin indices (from the legacy 2.6.35-palm tree). */
+#define MSM8660_MPM_PIN_SDC3_DAT1 21
+#define MSM8660_MPM_PIN_SDC3_DAT3 22
+#define MSM8660_MPM_PIN_SDC4_DAT1 23
+#define MSM8660_MPM_PIN_SDC4_DAT3 24
+
+struct device;
+struct device_node;
+struct msm8660_mpm;
+
+#if IS_ENABLED(CONFIG_QCOM_MSM8660_MPM)
+
+struct msm8660_mpm *msm8660_mpm_get(struct device *consumer,
+ struct device_node *np,
+ const char *propname);
+
+int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm, unsigned int pin,
+ bool on);
+int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
+ bool enable);
+int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm, unsigned int pin,
+ unsigned int flow_type);
+
+#else /* !CONFIG_QCOM_MSM8660_MPM */
+
+static inline struct msm8660_mpm *
+msm8660_mpm_get(struct device *consumer, struct device_node *np,
+ const char *propname)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline int msm8660_mpm_set_pin_wake(struct msm8660_mpm *mpm,
+ unsigned int pin, bool on)
+{
+ return -ENODEV;
+}
+
+static inline int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm,
+ unsigned int pin, bool enable)
+{
+ return -ENODEV;
+}
+
+static inline int msm8660_mpm_set_pin_type(struct msm8660_mpm *mpm,
+ unsigned int pin,
+ unsigned int flow_type)
+{
+ return -ENODEV;
+}
+
+#endif /* CONFIG_QCOM_MSM8660_MPM */
+
+#endif /* __SOC_QCOM_MSM8660_MPM_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
` (14 preceding siblings ...)
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller 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)
15 siblings, 6 replies; 46+ 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] 46+ 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] thermal: qcom: add PM8901 PMIC temperature-alarm driver 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; 46+ 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] 46+ messages in thread
* [PATCH v2 2/3] dt-bindings: thermal: qcom: add pm8901-temp-alarm
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver 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; 46+ 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] 46+ messages in thread
* [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver 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
2026-05-31 4:41 ` sashiko-bot
5 siblings, 1 reply; 46+ 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] 46+ messages in thread
* Re: [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
@ 2026-05-31 4:14 ` sashiko-bot
2026-05-31 7:58 ` Krzysztof Kozlowski
1 sibling, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:14 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing overarching schema updates in `qcom-soc.yaml` for legacy-formatted compatible strings.
--
commit 5610427121060301089bb1b2b018a18be452795c
Author: Herman van Hazendonk <github.com@herrie.org>
dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
This patch adds MSM8x60 family compatibles to the qcom,lcc dt-bindings schema.
It includes them in the main enum and the existing parent-clock-name
conditional block that already covers apq8064 and msm8960.
> diff --git a/Documentation/devicetree/bindings/clock/qcom,lcc.yaml b/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
> index 55985e562a34f..bbdad157f03fe 100644
> --- a/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
> +++ b/Documentation/devicetree/bindings/clock/qcom,lcc.yaml
> @@ -12,9 +12,12 @@ maintainers:
> properties:
> compatible:
> enum:
> + - qcom,lcc-apq8060
> - qcom,lcc-apq8064
> - qcom,lcc-ipq8064
> - qcom,lcc-mdm9615
> + - qcom,lcc-msm8260
> + - qcom,lcc-msm8660
> - qcom,lcc-msm8960
[Severity: Low]
Does Documentation/devicetree/bindings/arm/qcom-soc.yaml also need to be
updated to include these new compatibles?
Because these new compatibles use the legacy qcom,<ip>-<soc> format, they
fail the preferred pattern validation in qcom-soc.yaml. Usually, legacy
formatted strings are whitelisted in the overarching qcom-soc.yaml schema
under properties/compatible/oneOf/enum, where qcom,lcc-msm8960 and others
currently reside:
Documentation/devicetree/bindings/arm/qcom-soc.yaml:
- enum:
- qcom,dsi-ctrl-6g-qcm2290
- qcom,gpucc-sdm630
- qcom,gpucc-sdm660
- qcom,lcc-apq8064
- qcom,lcc-ipq8064
- qcom,lcc-mdm9615
- qcom,lcc-msm8960
Will omitting them from this whitelist cause dtbs_check validation to fail
when evaluating board device trees?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780197511.git.github.com@herrie.org?part=1
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
@ 2026-05-31 4:20 ` sashiko-bot
2026-05-31 8:01 ` Krzysztof Kozlowski
1 sibling, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:20 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Missing memory barrier and doorbell notification to RPM after clearing pending interrupts.
- [Medium] The `qcom,mpm-pin-map` property is required by the driver but missing from the YAML schema's required list, while `qcom,mpm-pin-count` is strictly required by the schema but ignored by the driver.
- [Medium] The device tree binding defines a `reg` property that explicitly overlaps with the RPM control block, an MFD/syscon architectural anti-pattern.
- [Medium] The binding and driver omit hardware wake pins from the DT mapping, relying instead on a custom driver C API.
--
commit ff9b33b822acb8b5cceef507bbe942c3ff3618b7
Author: Herman van Hazendonk <github.com@herrie.org>
dt-bindings: interrupt-controller: qcom: add msm8660-mpm
Add the binding for the Modem Power Manager (MPM) interrupt
controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The MPM
is a small wake-capable interrupt aggregator that lets the SoC stay
in low-power states while a small set of GIC SPIs continues to be
monitored and can wake the system on an edge.
> diff --git a/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
> new file mode 100644
> index 0000000000000..1288e83d75d6b
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interrupt-controller/qcom,msm8660-mpm.yaml
[ ... ]
> +properties:
> + compatible:
> + const: qcom,msm8660-mpm
> +
> + reg:
> + maxItems: 1
> + description: |
> + vMPM register window inside the RPM control block. Must cover
> + the request registers at offset 0 of the window and the status
> + registers at offset 0x420 of the window. A length of 0x440 is
> + sufficient for the four-word request banks and the status bank.
[Severity: Medium]
Does this explicit overlap violate standard Device Tree architecture?
Overlapping hardware resources should typically be modeled as child nodes of
an MFD sharing a regmap, or accessed via a syscon phandle. By directly using
devm_ioremap() in the driver to bypass standard resource reservation, this
introduces an architectural anti-pattern:
drivers/irqchip/irq-msm8660-mpm.c:msm8660_mpm_probe() {
/*
* Use a non-exclusive mapping: the qcom,rpm driver maps the
* surrounding RPM control block via its own platform resource,
* and our vMPM sub-region overlaps that mapping. devm_ioremap()
* does not call request_mem_region() so there is no conflict.
*/
mpm->base = devm_ioremap(dev, res->start, resource_size(res));
}
[ ... ]
> + interrupts:
> + maxItems: 1
> + description:
> + IPC IRQ raised by MPM when one of the enabled wake sources fires.
> + On MSM8x60 this is wired to GIC SPI 2.
[Severity: High]
Does the interrupt handler in the driver lack a memory barrier and doorbell
notification after clearing pending interrupts?
Looking at drivers/irqchip/irq-msm8660-mpm.c:msm8660_mpm_irq(), the CLEAR
register is written using a relaxed IO accessor:
drivers/irqchip/irq-msm8660-mpm.c:msm8660_mpm_irq() {
...
if (pending[i])
msm8660_mpm_write(mpm,
MSM8660_MPM_REG_CLEAR + i * 4, pending[i]);
...
}
Since the RPM caches the vMPM request registers, won't it miss the CLEAR
command without a doorbell notification, causing it to continue asserting the
IPC wake interrupt?
Also, without a memory barrier, this might reorder against the subsequent
parent GIC EOI. Could this cause an immediate spurious interrupt storm since
the parent IPC interrupt is level-triggered?
[ ... ]
> + qcom,mpm-pin-map:
> + $ref: /schemas/types.yaml#/definitions/uint32-matrix
> + items:
> + items:
> + - description: MPM pin number
> + - description: GIC SPI number this pin maps to
> + description: |
> + List of (MPM-pin, GIC-SPI) tuples for wake sources that have a
> + corresponding GIC IRQ. Consumers route their interrupts through
> + the MPM hierarchical irqdomain to take advantage of these
> + mappings.
> +
> + Raw wake pins like SDC3/4 DATx (pins 21-24) are NOT listed
> + here - they have no GIC SPI mapping and are accessed through
> + the msm8660_mpm_set_pin_wake() / msm8660_mpm_enable_pin() C API
> + by consumer drivers.
[Severity: Medium]
Is it correct to omit hardware wake pins from the device tree mapping and rely
on a custom C API?
Device tree bindings should describe hardware resources completely and
independently of OS implementation. Omitting pins just because they don't map
to a GIC SPI forces consumers to be tightly coupled to this specific driver
implementation via C symbol exports:
drivers/irqchip/irq-msm8660-mpm.c:msm8660_mpm_enable_pin()
Would it be better to list these pins and avoid bypassing standard kernel
subsystems like dummy IRQ domains or pinctrl frameworks?
[ ... ]
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - mboxes
> + - interrupt-controller
> + - '#interrupt-cells'
> + - qcom,mpm-pin-count
[Severity: Medium]
Are the required properties synchronized with the driver's expectations?
The schema enforces qcom,mpm-pin-count, but the driver ignores this property
and hardcodes the pin count to 64:
drivers/irqchip/irq-msm8660-mpm.c:
#define MSM8660_MPM_PIN_COUNT 64
Conversely, the driver's probe unconditionally fails if qcom,mpm-pin-map is
missing, returning -EINVAL:
drivers/irqchip/irq-msm8660-mpm.c:msm8660_mpm_probe() {
...
ret = of_property_count_u32_elems(np, "qcom,mpm-pin-map");
if (ret < 0 || ret % 2)
return dev_err_probe(dev, -EINVAL,
"invalid qcom,mpm-pin-map\n");
...
}
Should qcom,mpm-pin-map be added to the required list to prevent invalid device
trees from silently passing schema validation?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780195817.git.github.com@herrie.org?part=1
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
2026-05-31 4:09 ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
@ 2026-05-31 4:23 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:23 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The new `qcom,lcc-msm8660`, `qcom,lcc-msm8260`, and `qcom,lcc-apq8060` compatibles were not added to the legacy naming enumerations in `qcom-soc.yaml`.
--
commit f40b14d3bc25d211dba1f4b6ea323fcbe8e84acb
Author: Herman van Hazendonk <github.com@herrie.org>
dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs
Add the dt-binding clock-ID header for the MSM8x60 family
(MSM8260/MSM8660/APQ8060) Low Power Audio SubSystem Clock Controller
(LCC). It mirrors the format and ID range of the existing LCC headers
for newer Qualcomm SoCs.
> diff --git a/include/dt-bindings/clock/qcom,lcc-msm8660.h b/include/dt-bindings/clock/qcom,lcc-msm8660.h
> new file mode 100644
> index 0000000000000..d5d9b0d71a78f
> --- /dev/null
> +++ b/include/dt-bindings/clock/qcom,lcc-msm8660.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +/*
> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + */
> +
> +#ifndef _DT_BINDINGS_CLK_LCC_MSM8660_H
> +#define _DT_BINDINGS_CLK_LCC_MSM8660_H
> +
> +/*
> + * MSM8x60 family (MSM8260/MSM8660/APQ8060) LPASS Clock Controller (LCC)
> + * clock IDs. These are compatible with MSM8960 LCC as MSM8x60 and
> + * MSM8960 share the same audio subsystem clock architecture.
> + */
[Severity: Low]
Since this series introduces new compatibles for the MSM8x60 family
(qcom,lcc-msm8660, qcom,lcc-msm8260, and qcom,lcc-apq8060), do these
legacy format strings also need to be added to the generic SoC naming
constraints allowlist?
Without adding them to Documentation/devicetree/bindings/arm/qcom-soc.yaml,
will device tree schema validation fail for boards using them? They match
the generic regex but aren't in the legacy permitted list:
Documentation/devicetree/bindings/arm/qcom-soc.yaml:
- enum:
- qcom,dsi-ctrl-6g-qcm2290
- qcom,gpucc-sdm630
- qcom,gpucc-sdm660
- qcom,lcc-apq8064
- qcom,lcc-ipq8064
- qcom,lcc-mdm9615
- qcom,lcc-msm8960
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780197511.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
@ 2026-05-31 4:32 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:32 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Unprotected read-modify-write operations on shared vMPM registers lead to lost configuration updates.
- [High] Calling mbox_send_message() from irqchip callbacks causes a sleeping-while-atomic deadlock on PREEMPT_RT.
- [High] Identity confusion between MPM pin numbers and parent GIC SPI numbers leads to allocation failures or dropped interrupts.
- [High] Toggling the hardware ENABLE bit inside irq_set_wake inadvertently disables unmasked interrupts upon resume.
- [High] Missing suppress_bind_attrs allows sysfs unbind, causing Use-After-Free via irq_domain_remove and concurrent probes.
- [Medium] Missing read-back flush after clearing MPM interrupt status using writel_relaxed.
- [Low] Phandle resolution ignores the actual target node, leading to incorrect device linking.
--
commit c09bf824269ef596633ac65838f83c2f22429764
Author: Herman van Hazendonk <github.com@herrie.org>
irqchip: add MSM8x60 MPM wakeup interrupt controller driver
Add a driver for the MSM Power Manager (MPM) on the MSM8x60 family
(MSM8260/MSM8660/APQ8060). The MPM latches edge-triggered interrupts
during APPS power collapse and signals the RPM to wake the APPS back up.
> diff --git a/drivers/irqchip/irq-msm8660-mpm.c b/drivers/irqchip/irq-msm8660-mpm.c
> new file mode 100644
> index 0000000000000..b8394d9c076d2
> --- /dev/null
> +++ b/drivers/irqchip/irq-msm8660-mpm.c
> @@ -0,0 +1,740 @@
[ ... ]
> +static void msm8660_mpm_doorbell(struct msm8660_mpm *mpm)
> +{
> + int ret;
> +
> + if (!mpm->mbox_chan)
> + return;
> +
> + ret = mbox_send_message(mpm->mbox_chan, NULL);
[Severity: High]
Does calling mbox_send_message() from within an irqchip callback cause a
sleeping-while-atomic deadlock on PREEMPT_RT?
The irq_mask, irq_unmask, and irq_set_type callbacks run with the
raw_spinlock_t irq_desc->lock held.
The mailbox core conditionally waits and takes chan->lock, which is a spinlock_t
that gets converted to a sleepable RT-mutex on PREEMPT_RT.
Deferring the mailbox communication using irq_bus_lock and irq_bus_sync_unlock
could avoid this deadlock.
[ ... ]
> +static irqreturn_t msm8660_mpm_irq(int irq, void *data)
> +{
[ ... ]
> + /*
> + * Clear before dispatching: a new edge latched on this pin
> + * after this point will set the pending bit again and we
> + * will service it on the next IPC IRQ. Clearing AFTER the
> + * handler would race with that new latch and silently lose
> + * the new edge.
> + */
> + if (pending[i])
> + msm8660_mpm_write(mpm,
> + MSM8660_MPM_REG_CLEAR + i * 4, pending[i]);
[Severity: Medium]
Can a delayed write in the CPU store buffer overwrite newly latched edges and
drop interrupts?
Using writel_relaxed does not flush the write over the interconnect. If a
child driver triggers a new edge on a peripheral during the handler, the MPM
hardware will latch it.
If the delayed REG_CLEAR write arrives afterward, it will incorrectly wipe out
the newly latched edge. Adding a dummy read from the MPM register space ensures
the write completes before executing the handler.
[ ... ]
> +static int msm8660_mpm_hwirq_to_pin(struct msm8660_mpm *mpm, unsigned int hwirq)
> +{
> + int i;
> +
> + for (i = 0; i < mpm->pin_map_count; i++) {
> + if (mpm->pin_map[i].hwirq == hwirq)
[Severity: High]
Is the GIC SPI number being confused with the MPM pin number here?
When msm8660_mpm_set_type calls msm8660_mpm_hwirq_to_pin(mpm, d->hwirq),
it searches for mpm->pin_map[i].hwirq == hwirq.
Because d->hwirq is the MPM pin and pin_map[i].hwirq is the GIC SPI, this lookup
will fail and return -1, causing set_type to return -ENOENT.
> + return mpm->pin_map[i].pin;
> + }
> + return -1;
> +}
> +
> +static void msm8660_mpm_enable_hwirq(struct irq_data *d, bool enable)
> +{
> + struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
> + int pin;
> + u32 val, mask;
> +
> + pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
> + if (pin < 0)
> + return;
> +
> + mask = BIT(pin % 32);
> + val = msm8660_mpm_read(mpm,
> + MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
[Severity: High]
Could this concurrent read-modify-write corrupt the shared register state?
The driver manipulates shared 32-bit registers (ENABLE, DETECT_CTL,
POLARITY) to configure individual pins.
Because the IRQ subsystem calls irq_mask and irq_unmask with the per-interrupt
irq_desc->lock held, concurrent mask/unmask operations on different interrupts
within the same bank will execute without mutual exclusion.
A driver-local raw_spinlock_t is needed to serialize these accesses.
> + if (enable)
> + val |= mask;
> + else
> + val &= ~mask;
> + msm8660_mpm_write(mpm,
> + MSM8660_MPM_REG_ENABLE + (pin / 32) * 4, val);
> +
> + msm8660_mpm_doorbell(mpm);
[Severity: High]
Is it safe to invoke the doorbell from a context where a raw_spinlock_t
is held?
The irq_mask and irq_unmask callbacks execute with irq_desc->lock held.
Calling msm8660_mpm_doorbell() here will invoke mbox_send_message(), which
sleeps on PREEMPT_RT, causing a scheduling-while-atomic deadlock.
[ ... ]
> +static int msm8660_mpm_set_type(struct irq_data *d, unsigned int type)
> +{
> + struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
> + u32 detect, polarity, mask;
> + int pin;
> +
> + pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
> + if (pin < 0)
> + return -ENOENT;
> +
> + mask = BIT(pin % 32);
> + detect = msm8660_mpm_read(mpm,
> + MSM8660_MPM_REG_DETECT_CTL + (pin / 32) * 4);
[Severity: High]
Does this unlocked read-modify-write of the DETECT_CTL and POLARITY registers
race with other set_type calls?
Similar to the ENABLE register, these are shared across multiple pins and
need mutual exclusion to prevent lost configuration updates.
[ ... ]
> +static int msm8660_mpm_set_wake(struct irq_data *d, unsigned int on)
> +{
> + struct msm8660_mpm *mpm = irq_data_get_irq_chip_data(d);
> + int pin;
> +
> + pin = msm8660_mpm_hwirq_to_pin(mpm, d->hwirq);
> + if (pin < 0)
> + return -ENOENT;
> +
> + msm8660_mpm_enable_hwirq(d, !!on);
[Severity: High]
Will toggling the hardware ENABLE bit here cause the interrupt to remain
permanently disabled after the first system resume?
When the system suspends, IRQCHIP_MASK_ON_SUSPEND ensures non-wake
interrupts are logically masked. During resume, device drivers call
irq_set_wake(..., 0) to disable wakeup.
Since this writes 0 to the hardware ENABLE bit, but the IRQ core assumes
the interrupt is still logically unmasked, it will not call irq_unmask()
and the hardware state will remain disabled.
[ ... ]
> +static int msm8660_mpm_domain_alloc(struct irq_domain *domain,
> + unsigned int virq, unsigned int nr_irqs,
> + void *data)
> +{
[ ... ]
> + parent_fwspec.fwnode = domain->parent->fwnode;
> + parent_fwspec.param_count = 3;
> + parent_fwspec.param[0] = 0;
> + parent_fwspec.param[1] = hwirq;
[Severity: High]
Does passing the MPM pin number directly to the parent GIC domain map the
wrong interrupt?
The parent GIC domain expects a GIC SPI number in param[1], not the MPM pin
number extracted from fwspec->param[0]. The correct mapped SPI should be
looked up and passed here instead.
[ ... ]
> +struct msm8660_mpm *msm8660_mpm_get(struct device *consumer,
> + struct device_node *np,
> + const char *propname)
> +{
> + struct device_node *mpm_np;
> + struct device_link *link;
> +
> + if (!msm8660_mpm_global)
> + return ERR_PTR(-EPROBE_DEFER);
> +
> + if (np && propname) {
> + mpm_np = of_parse_phandle(np, propname, 0);
> + if (!mpm_np)
> + return ERR_PTR(-ENOENT);
> + of_node_put(mpm_np);
[Severity: Low]
Should this function verify that the resolved mpm_np actually matches
msm8660_mpm_global->dev->of_node?
If a consumer's DT property points to a completely different node by mistake,
it appears this code will still drop the reference and successfully return
the global MPM handle anyway.
> + }
> +
> + if (!consumer)
> + return msm8660_mpm_global;
> +
> + link = device_link_add(consumer, msm8660_mpm_global->dev,
> + DL_FLAG_AUTOREMOVE_CONSUMER);
[Severity: High]
Can msm8660_mpm_global->dev be dereferenced safely here without a lock?
If this races with a sysfs unbind of the MPM driver, msm8660_mpm_global
could be set to NULL or freed, leading to a use-after-free.
[ ... ]
> +int msm8660_mpm_enable_pin(struct msm8660_mpm *mpm, unsigned int pin,
> + bool enable)
> +{
> + u32 val;
> +
> + if (!mpm || pin >= MSM8660_MPM_PIN_COUNT)
> + return -EINVAL;
> +
> + val = msm8660_mpm_read(mpm,
> + MSM8660_MPM_REG_ENABLE + (pin / 32) * 4);
[Severity: High]
Does this raw-pin consumer API modify the ENABLE register without locking?
Concurrent calls from different consumer drivers could overwrite each other's
enable states without mutual exclusion.
[ ... ]
> +static void msm8660_mpm_remove(struct platform_device *pdev)
> +{
> + struct msm8660_mpm *mpm = platform_get_drvdata(pdev);
> +
> + /*
> + * Tear down in strict reverse order: drop the singleton so new
> + * consumers cannot grab a handle, free the IRQ so the handler
> + * cannot fire again, free the mailbox channel, then remove the
> + * domain. Consumer device_links established in msm8660_mpm_get()
> + * prevent the parent device from being unbound while a consumer
> + * still holds a handle.
> + */
> + msm8660_mpm_global = NULL;
> + free_irq(mpm->parent_irq, mpm);
> + if (mpm->mbox_chan)
> + mbox_free_channel(mpm->mbox_chan);
> + irq_domain_remove(mpm->domain);
[Severity: High]
Does allowing sysfs unbind lead to a use-after-free in the core IRQ subsystem?
While consumer drivers are unbound due to the device_link, their virtual IRQ
mappings persist in the core IRQ subsystem because they do not automatically
call irq_dispose_mapping().
Removing the domain here frees the domain memory, but the core IRQ subsystem
retains dangling pointers to it in irq_data structures.
> +}
[ ... ]
> +static struct platform_driver msm8660_mpm_driver = {
> + .probe = msm8660_mpm_probe,
> + .remove = msm8660_mpm_remove,
> + .driver = {
> + .name = "msm8660-mpm",
[Severity: High]
Should this driver set .suppress_bind_attrs = true?
Without it, the driver can be unbound via sysfs, which triggers the
irq_domain_remove() use-after-free issue described above.
> + .of_match_table = msm8660_mpm_of_match,
> + },
> +};
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780195817.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver
2026-05-31 4:09 ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
@ 2026-05-31 4:33 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:33 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Missing Clock Parent `pll4_vote` leading to orphaned clocks.
Pre-existing issues:
- [High] Race condition between RCG clock `set_rate` and Branch clock `enable`/`disable` sharing the same register.
--
commit 0c6b3f0cf3eb1cc15e3add96b0433ff823210d70
Author: Herman van Hazendonk <github.com@herrie.org>
clk: qcom: add MSM8x60 LCC (LPASS) driver
This commit adds a clock driver for the LPASS Clock Controller on the
MSM8x60 family. It handles the specific register layout, muxing, and
divider topology for this generation, ensuring LPASS audio DSP clocks
work correctly before audio playback or capture.
> diff --git a/drivers/clk/qcom/lcc-msm8660.c b/drivers/clk/qcom/lcc-msm8660.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/clk/qcom/lcc-msm8660.c
[ ... ]
> +static const struct clk_parent_data lcc_pxo_pll4[] = {
> + { .fw_name = "pxo", .name = "pxo_board" },
> + { .fw_name = "pll4_vote", .name = "pll4_vote" },
> +};
[Severity: High]
Does this correctly reference the local PLL4?
The driver registers the local PLL simply as "pll4" (via clk_pll pll4),
and unlike MSM8960, the MSM8660 platform doesn't appear to export a
"pll4_vote" clock from the global clock controller.
Because the clock framework cannot find "pll4_vote", will parent
resolution fail and leave the essential LPASS root clocks orphaned?
Should this refer to the local PLL using .hw = &pll4.clkr.hw or
.name = "pll4" instead?
[ ... ]
> +#define CLK_AIF_OSR_SRC(prefix, _ns, _md) \
> +static struct clk_rcg prefix##_osr_src = { \
> + .ns_reg = _ns, \
[ ... ]
> +#define CLK_AIF_OSR_CLK(prefix, _ns, hr, en_bit) \
> +static struct clk_branch prefix##_osr_clk = { \
> + .halt_reg = hr, \
> + .halt_bit = 1, \
> + .halt_check = BRANCH_HALT_ENABLE, \
> + .clkr = { \
> + .enable_reg = _ns, \
[ ... ]
> +CLK_AIF_OSR_SRC(mi2s, 0x48, 0x4c)
> +CLK_AIF_OSR_CLK(mi2s, 0x48, 0x50, 17)
[Severity: High]
This is a pre-existing issue, but does sharing the ns_reg (e.g., 0x48)
between the RCG clock and Branch clock expose a race condition?
The RCG clock's set_rate callback (__clk_rcg_set_rate) performs a
non-atomic read-modify-write on this register protected only by the
global CCF prepare_lock mutex.
Concurrently, a branch clock's enable callback (clk_enable_regmap)
performs an atomic regmap_update_bits on the same register protected
by the global CCF enable_lock spinlock.
Since these two locks don't serialize against each other, can an
interrupt-driven clk_enable preempt the clk_set_rate thread between
its read and write steps?
If clk_set_rate writes its stale value back, it could revert the
branch clock's hardware enable state, potentially triggering a fatal
external abort on unclocked register access.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780197511.git.github.com@herrie.org?part=3
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-31 4:09 ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-05-31 4:34 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:34 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Clock prepare/enable reference leak on probe deferral when RPM device lookup fails.
- [High] Time-of-check to time-of-use (TOCTOU) race condition in `msm8660_get_rpm` leading to Use-After-Free.
- [High] Integer truncation in the fabric clock rate calculation allows unprivileged processes to freeze the interconnect bus.
- [Low] `COUNT_ARGS(__VA_ARGS__)` evaluates to 1 when no arguments are passed, creating bogus links to node ID 0.
--
commit 3b79422423710904c503ef7d19429d69c2f02d18
Author: Herman van Hazendonk <github.com@herrie.org>
interconnect: qcom: add MSM8x60 NoC driver
Add a Qualcomm interconnect driver for the MSM8x60 family, modelling the
four fabrics that connect masters and slaves on these Scorpion-class SoCs.
The driver implements the interconnect-provider API to manage bandwidth
between specific masters and slaves.
> diff --git a/drivers/interconnect/qcom/msm8660.c b/drivers/interconnect/qcom/msm8660.c
> new file mode 100644
> index 0000000000000..174bc59da74f7
> --- /dev/null
> +++ b/drivers/interconnect/qcom/msm8660.c
> @@ -0,0 +1,1069 @@
[ ... ]
> +#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__), \
[Severity: Low]
Does COUNT_ARGS(__VA_ARGS__) evaluate to 1 when invoked with no links?
When DEFINE_QNODE is invoked with an empty variadic argument list, this
might receive a single empty token and initialize .links to { 0 },
causing a phantom node with ID 0 to be created.
[ ... ]
> +static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
> +{
[ ... ]
> + /* Apply minimum floor to prevent bus starvation */
> + rate = max_t(u64, rate, MSM8660_FABRIC_MIN_RATE);
> + rate = min_t(u32, rate, INT_MAX);
[Severity: High]
Can this truncation bypass the minimum floor limit?
If rate exceeds the 32-bit maximum (for example, during very high
bandwidth requests), the cast to u32 in min_t() will truncate the upper
bits. This could result in a near-zero clock rate instead of capping it,
potentially freezing the interconnect bus.
[ ... ]
> +static struct qcom_rpm *msm8660_get_rpm(struct device *dev)
> +{
[ ... ]
> + 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);
[Severity: High]
Could this lead to a race condition between retrieving the pointer and
pinning the device?
Because dev_get_drvdata() retrieves the pointer before device_link_add()
pins the supplier, a concurrent unbind and rebind of the supplier in this
window could cause the original pointer to be freed, even though the link
might succeed on the rebound instance, leading to a use-after-free.
[ ... ]
> +static int msm8660_icc_probe(struct platform_device *pdev)
> +{
[ ... ]
> + 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);
[Severity: High]
Does returning directly here leak the clock prepare/enable reference?
When msm8660_get_rpm() returns -EPROBE_DEFER, returning directly skips
the rollback for the clk_bulk_prepare_enable() call made earlier. This
will leak the clock reference on every probe retry.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780197411.git.github.com@herrie.org?part=2
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver
2026-05-31 4:09 ` [PATCH v2 3/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk
@ 2026-05-31 4:41 ` sashiko-bot
0 siblings, 0 replies; 46+ messages in thread
From: sashiko-bot @ 2026-05-31 4:41 UTC (permalink / raw)
To: Herman van Hazendonk; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The driver disables the PMIC's hardware thermal auto-shutdown during probe but does not implement a `.shutdown` callback to restore it, leaving the device physically unprotected across warm reboots.
- [High] The driver overrides the PMIC's hardware thermal shutdown but fails to configure the thermal interrupts as system wakeup sources or restore the hardware shutdown during suspend, leaving the device unprotected while suspended.
--
commit ea0822f7dd85ca701edd835f63cf382da86b80eb
Author: Herman van Hazendonk <github.com@herrie.org>
thermal: qcom: add PM8901 PMIC temperature-alarm driver
Add a thermal-of sensor driver for the temperature-alarm block inside
the Qualcomm PM8901 PMIC. It exposes an over-temperature alarm at SSBI
offset 0x23/0x24 with three escalating stages.
> diff --git a/drivers/thermal/qcom/qcom-pm8901-tm.c b/drivers/thermal/qcom/qcom-pm8901-tm.c
> new file mode 100644
> index 0000000000000..b159e9917c2c9
> --- /dev/null
> +++ b/drivers/thermal/qcom/qcom-pm8901-tm.c
[ ... ]
> + 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);
[Severity: High]
Since the PMIC's hardware thermal shutdown is overridden by the software
driver during probe, should these thermal interrupts be configured as system
wakeup sources?
During system suspend, non-wakeup interrupts are disabled and the software
thermal core sleeps. Without wakeup enabled, could the device overheat while
suspended without the hardware or OS acting on it to prevent damage?
[ ... ]
> +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,
> +};
[Severity: High]
Is a .shutdown callback needed in pm8901_tm_driver to restore the PMIC's
hardware thermal auto-shutdown?
While pm8901_tm_enable_sw_override() registers a devm action to restore it
on unbind, devm actions are not executed during the system shutdown or reboot
path in device_shutdown().
Could this leave the device in software-override mode across a warm reboot,
where it would be physically unprotected if it overheats during the bootloader
phase before the OS boots?
Additionally, does this driver need suspend and resume PM callbacks in
dev_pm_ops to temporarily re-enable the hardware shutdown while the software
thermal core is asleep?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1780197411.git.github.com@herrie.org?part=3
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
2026-05-31 4:14 ` sashiko-bot
@ 2026-05-31 7:58 ` Krzysztof Kozlowski
1 sibling, 0 replies; 46+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-31 7:58 UTC (permalink / raw)
To: Herman van Hazendonk, Bjorn Andersson, Conor Dooley, devicetree,
Krzysztof Kozlowski, linux-arm-msm, linux-clk, linux-kernel,
Michael Turquette, Rob Herring, Stephen Boyd
On 31/05/2026 06:09, Herman van Hazendonk wrote:
> Add "qcom,lcc-msm8260", "qcom,lcc-msm8660" and "qcom,lcc-apq8060" to
> the qcom,lcc enum and to the existing parent-clock-name conditional
> block that already covers apq8064/msm8960.
>
> The MSM8x60 family (MSM8260/MSM8660/APQ8060) is the Scorpion-class
> generation preceding MSM8960; its LPASS Clock Controller is fed by
> the same pxo board source and pll4_vote parents as apq8064/msm8960,
> and the codec-fed slave-mode I2S/PCM clocks have the same names, so
> the existing apq8064/msm8960 conditional block applies unchanged
> once the new compatibles are listed.
>
> This allows board DTs using the new compatibles to pass
> dt_binding_check; the actual driver support is added by a later
> patch in this series.
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
> Documentation/devicetree/bindings/clock/qcom,lcc.yaml | 6 ++++++
> 1 file changed, 6 insertions(+)
This is a complete mess - one thread has like 10 different patchsets and
versions.
I have no clue what to do here. I am dropping everything from DT patchwork.
Please read carefully submitting patches.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 46+ 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; 46+ 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] 46+ 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; 46+ 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] 46+ messages in thread
* Re: [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
2026-05-31 4:20 ` sashiko-bot
@ 2026-05-31 8:01 ` Krzysztof Kozlowski
1 sibling, 0 replies; 46+ messages in thread
From: Krzysztof Kozlowski @ 2026-05-31 8:01 UTC (permalink / raw)
To: Herman van Hazendonk, Bjorn Andersson, Clark Williams,
Conor Dooley, devicetree, Konrad Dybcio, Krzysztof Kozlowski,
linux-arm-msm, linux-kernel, linux-rt-devel, Rob Herring,
Sebastian Andrzej Siewior, Steven Rostedt, Thomas Gleixner
On 31/05/2026 06:09, Herman van Hazendonk wrote:
> Add the binding for the Modem Power Manager (MPM) interrupt
> controller on the MSM8x60 family (MSM8260/MSM8660/APQ8060). The MPM
> is a small wake-capable interrupt aggregator that lets the SoC stay
> in low-power states while a small set of GIC SPIs continues to be
> monitored and can wake the system on an edge.
>
> The binding describes its register region (in the RPM syscon), the
> GIC SPI it raises on a wake event, the GIC mailbox it shares with
> the RPM, and the per-pin wake-source mapping table.
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
Dropping for the same reason as in other patches - mixed up with
everything else.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 46+ messages in thread
end of thread, other threads:[~2026-05-31 8:01 UTC | newest]
Thread overview: 46+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-30 13:59 [PATCH 0/2] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 1/3] dt-bindings: clock: qcom: add mmcc-msm8660 clock IDs Herman van Hazendonk
2026-05-30 13:58 ` [PATCH 2/3] dt-bindings: reset: qcom: add mmcc-msm8660 reset IDs Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 0/3] clk: qcom: add MSM8x60 Multimedia Clock Controller Herman van Hazendonk
2026-05-30 13:59 ` [PATCH 1/2] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
2026-05-30 14:15 ` sashiko-bot
2026-05-30 13:59 ` [PATCH 2/2] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
2026-05-30 14:25 ` sashiko-bot
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:14 ` sashiko-bot
2026-05-30 14:00 ` [PATCH 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
2026-05-30 14:00 ` [PATCH 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
2026-05-30 14:22 ` sashiko-bot
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 14:08 ` sashiko-bot
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-30 14:16 ` sashiko-bot
2026-05-31 4:08 ` [PATCH v2 0/3] clk: qcom: add MSM8x60 LPASS Clock Controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/3] dt-bindings: clock: qcom,lcc: add MSM8x60 family compatibles Herman van Hazendonk
2026-05-31 4:14 ` sashiko-bot
2026-05-31 7:58 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/3] dt-bindings: clock: qcom: add lcc-msm8660 LPASS clock IDs Herman van Hazendonk
2026-05-31 4:23 ` sashiko-bot
2026-05-31 4:09 ` [PATCH v2 3/3] clk: qcom: add MSM8x60 LCC (LPASS) driver Herman van Hazendonk
2026-05-31 4:33 ` sashiko-bot
2026-05-31 4:09 ` [PATCH v2 0/2] irqchip: add MSM8x60 MPM wakeup interrupt controller Herman van Hazendonk
2026-05-31 4:09 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: qcom: add msm8660-mpm Herman van Hazendonk
2026-05-31 4:20 ` sashiko-bot
2026-05-31 8:01 ` Krzysztof Kozlowski
2026-05-31 4:09 ` [PATCH v2 2/2] irqchip: add MSM8x60 MPM wakeup interrupt controller driver Herman van Hazendonk
2026-05-31 4:32 ` sashiko-bot
2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver 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:34 ` sashiko-bot
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
2026-05-31 4:41 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox