* [PATCH v3 0/2] interconnect: qcom: add MSM8x60 NoC driver
@ 2026-06-06 12:34 Herman van Hazendonk
2026-06-06 12:34 ` [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk
2026-06-06 12:34 ` [PATCH v3 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
0 siblings, 2 replies; 7+ messages in thread
From: Herman van Hazendonk @ 2026-06-06 12:34 UTC (permalink / raw)
To: Georgi Djakov, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel,
Herman van Hazendonk
Add an interconnect provider for the MSM8x60 family (MSM8260 / MSM8660
/ APQ8060). The driver describes the four NoC fabrics (System, Apps,
MMSS, Daytona) and the RPM-managed fabric arbitration tables that
program per-master / per-tiered-slave bandwidth budgets via
QCOM_RPM_*_FABRIC_ARB resources.
v3 folds one High Sashiko finding (msm8660_rpm_commit() concurrent
access on shared per-fabric scratch buffers) on top of the v2 patchset
that addressed the maintainer / LKP feedback from v1.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
Changes in v3:
- [High Sashiko] msm8660_rpm_commit() ran with no synchronisation.
The shared per-fabric scratch buffers (qp->arb, qp->bwsum,
qp->rpm_buf) are allocated once at probe and reused across all
ICC set operations. The ICC core can dispatch provider->set
concurrently from different CPUs, so two msm8660_rpm_commit()
instances could interleave their memset()s and overlapping
per-node writes, producing a corrupted packet on the
qcom_rpm_write() to RPM firmware. Added a 'struct mutex
commit_lock' to msm8660_icc_provider, initialised via
devm_mutex_init() in probe, taken across the entire
memset -> iterate -> pack -> qcom_rpm_write sequence with a
scoped guard(mutex)().
Changes in v2 (already sent 2026-06-04):
Maintainer / LKP feedback on v1:
- [Medium] dt-bindings: add explicit '#interconnect-cells: const: 1'.
The common rpm-common.yaml permits both 1- and 2-cell phandles for
historical reasons; without an explicit const here a 2-cell consumer
would pass schema validation but then fail of_icc_xlate_onecell()
at runtime.
- [High] msm8660_get_rpm(): drop the manual device_link_remove()
before the EPROBE_DEFER return path. The link is added with
DL_FLAG_AUTOREMOVE_CONSUMER, so the driver core releases its kref
when our probe ultimately fails. The manual remove dropped an
extra kref -> potential UAF on the supplier device.
- [Low] devm_clk_bulk_get_optional(): drop the dead ret == -ENOENT
branch. The helper internally masks per-clock ENOENT to NULL
handles before returning, so the branch is unreachable.
- [High] static msm8660_qnode .node cache pointers: on probe
failure icc_nodes_remove() frees the dynamic icc_node but the
cache field still points at freed memory. A subsequent
probe-retry (or unbind/rebind) would then dereference the stale
pointer. Added msm8660_clear_node_cache() helper, called from
both err_remove_nodes: and msm8660_icc_remove(). Same latent bug
exists in the icc-rpmh.c template; we keep it local for now.
- [High] icc_link_nodes(): check the return value. Failure
(-ENOMEM from krealloc) was silently ignored -> partially-linked
topology with no error path. Now goto err_remove_nodes on
failure.
Test results (v3, HP TouchPad APQ8060, kernel 7.1.0-rc1):
- All four fabrics probe clean at boot
(4096000.qnoc, 4400000.qnoc, 4500000.qnoc, 4540000.qnoc).
Real bandwidth votes flow through slv_ebi_ch0 (DDR), MDP,
video-codec, camss.
- 120s concurrent multi-subsystem load
(camera stream loop + dd to eMMC + /dev/urandom + filesystem
walk + USB I/O): zero qcom_rpm errors, zero icc warnings,
USB stayed up, MDP stayed active, no display glitches or
USB stalls.
- dmesg post-load: 0 WARN, 0 BUG, 0 Oops. Specifically no
"qcom_rpm: ARB write failed" and no "interconnect: ... failed"
messages.
- Kernel build clean (ARCH=arm, CONFIG_INTERCONNECT_QCOM_MSM8660=y).
- Link to v1: https://lore.kernel.org/r/20260603163410.2312712-1-github.com@herrie.org
- Link to v2: https://lore.kernel.org/r/20260604184400.801543-1-github.com@herrie.org
---
Herman van Hazendonk (2):
dt-bindings: interconnect: qcom: add msm8660 NoC
interconnect: qcom: add MSM8x60 NoC driver
.../bindings/interconnect/qcom,msm8660.yaml | 166 ++
drivers/interconnect/qcom/Kconfig | 14 +
drivers/interconnect/qcom/Makefile | 2 +
drivers/interconnect/qcom/msm8660.c | 1663 ++++++++++++++++++++
include/dt-bindings/interconnect/qcom,msm8660.h | 156 ++
5 files changed, 2001 insertions(+)
---
base-commit: 944125b4c454b58d2fe6e35f1087a932b2050dff
change-id: 20260606-submit-interconnect-msm8660-62420c194b6a
Best regards,
--
Herman van Hazendonk <github.com@herrie.org>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC
2026-06-06 12:34 [PATCH v3 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-06-06 12:34 ` Herman van Hazendonk
2026-06-06 12:46 ` sashiko-bot
2026-06-06 13:23 ` Dmitry Baryshkov
2026-06-06 12:34 ` [PATCH v3 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
1 sibling, 2 replies; 7+ messages in thread
From: Herman van Hazendonk @ 2026-06-06 12:34 UTC (permalink / raw)
To: Georgi Djakov, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel,
Herman van Hazendonk
Add a dt-binding schema and an interconnect master/slave ID header for
the MSM8x60 family (MSM8260/MSM8660/APQ8060) Network-on-Chip. The
chip exposes four NoC 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)
The schema covers all four compatible strings, per-fabric clock-name
lists (bus / bus_a / ebi1 / ebi1_a for AFAB; bus / bus_a / smi /
smi_a for MMFAB; bus / bus_a for SFAB and DFAB), the required
qcom,rpm phandle through which the provider hands the arbitration
buffer to RPM firmware, and #interconnect-cells = <1>.
The ID header lists per-fabric master / slave / gateway indices
derived from the legacy vendor msm_bus_board_8660.c enums,
normalised to the upstream interconnect-framework naming convention.
Assisted-by: Claude:claude-opus-4-7 Sashiko:claude-haiku-4-5
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../bindings/interconnect/qcom,msm8660.yaml | 166 +++++++++++++++++++++
include/dt-bindings/interconnect/qcom,msm8660.h | 156 +++++++++++++++++++
2 files changed, 322 insertions(+)
diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
new file mode 100644
index 000000000000..ff28c7d46c32
--- /dev/null
+++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
@@ -0,0 +1,166 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interconnect/qcom,msm8660.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm MSM8x60 family Network-On-Chip interconnect
+
+maintainers:
+ - Herman van Hazendonk <github.com@herrie.org>
+
+description: |
+ The Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) NoC is split into
+ four fabrics, each modelled as a separate interconnect provider:
+
+ APPSS fabric Scorpion CPU cluster, L2 cache, EBI (DDR) memory.
+ System fabric DMA controllers, modem and LPASS cross-domain links,
+ security peripherals.
+ Multimedia fab Display (MDP), GPU, camera (VFE), video codec (VPE),
+ JPEG, with the SMI scratchpad as the local slave.
+ Daytona fabric SDC1..SDC5 controllers and ADM master/slave channels.
+
+ Each provider programs fabric arbitration (per-master priority tier and
+ per-slave bandwidth vote) through RPM firmware via the qcom,rpm parent
+ controller, in addition to scaling its bus clocks via clk_set_rate.
+
+properties:
+ compatible:
+ enum:
+ - qcom,msm8660-apps-fabric
+ - qcom,msm8660-system-fabric
+ - qcom,msm8660-mmss-fabric
+ - qcom,msm8660-daytona-fabric
+
+ '#interconnect-cells':
+ const: 1
+ description:
+ Each consumer phandle takes the form <provider node-id> where node-id
+ indexes the per-fabric ID array in
+ <dt-bindings/interconnect/qcom,msm8660.h>. qcom,rpm-common.yaml
+ permissively allows 2 cells (and 1 marked deprecated); pin this
+ provider to 1, which is what of_icc_xlate_onecell strictly expects.
+
+ clocks:
+ minItems: 2
+ maxItems: 4
+
+ clock-names:
+ minItems: 2
+ maxItems: 4
+
+ qcom,rpm:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the qcom,rpm node. The interconnect provider hands the
+ per-fabric arbitration buffer to RPM firmware through this resource,
+ so a production device tree must point at the system RPM.
+
+required:
+ - compatible
+ - clocks
+ - clock-names
+ - qcom,rpm
+ - '#interconnect-cells'
+
+allOf:
+ - $ref: qcom,rpm-common.yaml#
+ - if:
+ properties:
+ compatible:
+ const: qcom,msm8660-apps-fabric
+ then:
+ properties:
+ clocks:
+ items:
+ - description: APPSS fabric bus clock (active vote)
+ - description: APPSS fabric bus clock (active+sleep vote)
+ - description: EBI1 (DDR) channel clock (active vote)
+ - description: EBI1 (DDR) channel clock (active+sleep vote)
+ clock-names:
+ items:
+ - const: bus
+ - const: bus_a
+ - const: ebi1
+ - const: ebi1_a
+ - if:
+ properties:
+ compatible:
+ enum:
+ - qcom,msm8660-system-fabric
+ - qcom,msm8660-daytona-fabric
+ then:
+ properties:
+ clocks:
+ items:
+ - description: Fabric bus clock (active vote)
+ - description: Fabric bus clock (active+sleep vote)
+ clock-names:
+ items:
+ - const: bus
+ - const: bus_a
+ - if:
+ properties:
+ compatible:
+ const: qcom,msm8660-mmss-fabric
+ then:
+ properties:
+ clocks:
+ items:
+ - description: MMSS fabric bus clock (active vote)
+ - description: MMSS fabric bus clock (active+sleep vote)
+ - description: SMI scratchpad clock (active vote)
+ - description: SMI scratchpad clock (active+sleep vote)
+ clock-names:
+ items:
+ - const: bus
+ - const: bus_a
+ - const: smi
+ - const: smi_a
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/clock/qcom,rpmcc.h>
+
+ interconnect-afab {
+ compatible = "qcom,msm8660-apps-fabric";
+ clocks = <&rpmcc RPM_APPS_FABRIC_CLK>,
+ <&rpmcc RPM_APPS_FABRIC_A_CLK>,
+ <&rpmcc RPM_EBI1_CLK>,
+ <&rpmcc RPM_EBI1_A_CLK>;
+ clock-names = "bus", "bus_a", "ebi1", "ebi1_a";
+ qcom,rpm = <&rpm>;
+ #interconnect-cells = <1>;
+ };
+
+ interconnect-sfab {
+ compatible = "qcom,msm8660-system-fabric";
+ clocks = <&rpmcc RPM_SYS_FABRIC_CLK>,
+ <&rpmcc RPM_SYS_FABRIC_A_CLK>;
+ clock-names = "bus", "bus_a";
+ qcom,rpm = <&rpm>;
+ #interconnect-cells = <1>;
+ };
+
+ interconnect-mmfab {
+ compatible = "qcom,msm8660-mmss-fabric";
+ clocks = <&rpmcc RPM_MM_FABRIC_CLK>,
+ <&rpmcc RPM_MM_FABRIC_A_CLK>,
+ <&rpmcc RPM_SMI_CLK>,
+ <&rpmcc RPM_SMI_A_CLK>;
+ clock-names = "bus", "bus_a", "smi", "smi_a";
+ qcom,rpm = <&rpm>;
+ #interconnect-cells = <1>;
+ };
+
+ interconnect-dfab {
+ compatible = "qcom,msm8660-daytona-fabric";
+ clocks = <&rpmcc RPM_DAYTONA_FABRIC_CLK>,
+ <&rpmcc RPM_DAYTONA_FABRIC_A_CLK>;
+ clock-names = "bus", "bus_a";
+ qcom,rpm = <&rpm>;
+ #interconnect-cells = <1>;
+ };
+...
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] 7+ messages in thread
* [PATCH v3 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-06-06 12:34 [PATCH v3 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-06-06 12:34 ` [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk
@ 2026-06-06 12:34 ` Herman van Hazendonk
2026-06-06 12:48 ` sashiko-bot
1 sibling, 1 reply; 7+ messages in thread
From: Herman van Hazendonk @ 2026-06-06 12:34 UTC (permalink / raw)
To: Georgi Djakov, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley
Cc: linux-arm-msm, linux-pm, devicetree, linux-kernel,
Herman van Hazendonk
Add a Qualcomm interconnect driver for the MSM8x60 family modelling the
four NoC fabrics (APPSS, System, MMSS, Daytona) 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 via the RPM arbitration tables.
Each fabric carries:
- A bus clock (managed via clk_bulk APIs) whose rate is the maximum
per-node rate across the provider, following the icc-rpm.c
convention. Summing all provider nodes would overcount, because
the framework writes each path's bandwidth onto every node it
traverses; taking the max picks the hottest single node, which is
the correct fabric throughput requirement. A minimum floor of
384 MHz keeps the bus alive during concurrent USB and MDP activity
(266 MHz was empirically insufficient).
- An RPM arbitration buffer (arb / bwsum) that the RPM firmware
consumes via its shared-memory protocol; commits go via the
qcom-rpm driver's set_resource API.
Nodes follow the icc-rpmh-style pattern: each qnode is a static
struct with a flexible link_nodes[] array of qnode pointers, ICC IDs
are assigned dynamically via icc_node_create_dyn(), and links are
established with icc_link_nodes() at probe time. The dt-bindings
header IDs remain per-fabric indices into the of_icc_xlate_onecell
data array.
msm8660_get_rpm() pins the supplier with device_link_add() before
reading drvdata so an unbind/rebind window cannot leave a stale
qcom_rpm pointer; the EPROBE_DEFER paths route through
dev_err_ptr_probe() for proper deferred-probe accounting.
clk_bulk_prepare_enable() is paired with a devm_add_action_or_reset()
cleanup so an EPROBE_DEFER from the RPM lookup does not leak the
prepare/enable refcount across retries. The fabric rate cap uses
min_t(u64,...) so a bandwidth request exceeding 4 GiB/s cannot wrap
through u32 truncation into a near-zero clock rate that would halt
the interconnect.
Assisted-by: Claude:claude-opus-4-7 Sashiko:claude-haiku-4-5
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/interconnect/qcom/Kconfig | 14 +
drivers/interconnect/qcom/Makefile | 2 +
drivers/interconnect/qcom/msm8660.c | 1663 +++++++++++++++++++++++
include/dt-bindings/interconnect/qcom,msm8660.h | 6 +-
4 files changed, 1682 insertions(+), 3 deletions(-)
diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig
index 786b4eda44b4..579a4e3ea33e 100644
--- a/drivers/interconnect/qcom/Kconfig
+++ b/drivers/interconnect/qcom/Kconfig
@@ -80,6 +80,20 @@ config INTERCONNECT_QCOM_MSM8953
This is a driver for the Qualcomm Network-on-Chip on msm8953-based
platforms.
+config INTERCONNECT_QCOM_MSM8660
+ # bool, not tristate: this provider must be registered at
+ # core_initcall to be ready before icc_init (subsys_initcall) walks
+ # devicetree consumers; a loadable module cannot satisfy that
+ # ordering. Build-in only.
+ bool "Qualcomm MSM8x60 interconnect driver"
+ depends on INTERCONNECT_QCOM=y
+ depends on MFD_QCOM_RPM=y
+ 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..f7e7bd0374f9
--- /dev/null
+++ b/drivers/interconnect/qcom/msm8660.c
@@ -0,0 +1,1663 @@
+// 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 legacy vendor 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 legacy vendor 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/cleanup.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/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#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. legacy vendor kernel 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 legacy vendor kernel 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
+ * @node: backing icc_node pointer, populated at probe via icc_node_create_dyn()
+ * @num_links: the total number of @link_nodes
+ * @buswidth: width of the interconnect between a node and the bus (bytes)
+ * @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)
+ * @link_nodes: flexible array of pointers to qnodes reachable from this node
+ */
+struct msm8660_icc_node {
+ const char *name;
+ struct icc_node *node;
+ u16 num_links;
+ u16 buswidth;
+ s8 mas_port;
+ s8 slv_port;
+ u8 mas_tier;
+ struct msm8660_icc_node *link_nodes[];
+};
+
+/**
+ * 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)
+ * @commit_lock: serialises msm8660_rpm_commit(). The ICC core can dispatch
+ * provider->set concurrently from different CPUs; without this lock
+ * the shared @arb / @bwsum / @rpm_buf scratch buffers would race
+ * (interleaved memset() and overlapping per-node writes), corrupting
+ * the packet handed to qcom_rpm_write() and producing malformed
+ * fabric arbitration on the wire.
+ */
+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;
+ struct mutex commit_lock;
+};
+
+/*
+ * Forward declarations for all qnodes.
+ *
+ * Because qnode definitions now use pointer-based .link_nodes = { &foo, ... }
+ * with a flexible array member, every qnode that appears as a link target
+ * must be visible at the point of use. Forward-declaring all qnodes up front
+ * keeps the order-of-definition concern out of the per-fabric sections and
+ * mirrors the pattern used by drivers/interconnect/qcom/sa8775p.c.
+ */
+/* APPSS Fabric */
+static struct msm8660_icc_node mas_ampss_m0;
+static struct msm8660_icc_node mas_ampss_m1;
+static struct msm8660_icc_node slv_ebi_ch0;
+static struct msm8660_icc_node slv_ampss_l2;
+static struct msm8660_icc_node afab_to_mmss;
+static struct msm8660_icc_node afab_to_system;
+
+/* System Fabric */
+static struct msm8660_icc_node sfab_mas_appss;
+static struct msm8660_icc_node sfab_mas_sps;
+static struct msm8660_icc_node sfab_mas_adm0_port0;
+static struct msm8660_icc_node sfab_mas_adm0_port1;
+static struct msm8660_icc_node sfab_mas_adm1_port0;
+static struct msm8660_icc_node sfab_mas_adm1_port1;
+static struct msm8660_icc_node sfab_mas_lpass_proc;
+static struct msm8660_icc_node sfab_mas_mss_proci;
+static struct msm8660_icc_node sfab_mas_mss_procd;
+static struct msm8660_icc_node sfab_mas_mss_mdm_port0;
+static struct msm8660_icc_node sfab_mas_lpass;
+static struct msm8660_icc_node sfab_mas_mmss_fpb;
+static struct msm8660_icc_node sfab_mas_adm1_ci;
+static struct msm8660_icc_node sfab_mas_adm0_ci;
+static struct msm8660_icc_node sfab_mas_mss_mdm_port1;
+static struct msm8660_icc_node sfab_mas_usb_hs;
+static struct msm8660_icc_node sfab_to_appss;
+static struct msm8660_icc_node sfab_to_system_fpb;
+static struct msm8660_icc_node sfab_to_cpss_fpb;
+static struct msm8660_icc_node sfab_slv_sps;
+static struct msm8660_icc_node sfab_slv_system_imem;
+static struct msm8660_icc_node sfab_slv_ampss;
+static struct msm8660_icc_node sfab_slv_mss;
+static struct msm8660_icc_node sfab_slv_lpass;
+static struct msm8660_icc_node sfab_slv_mmss_fpb;
+static struct msm8660_icc_node sfab_to_dfab;
+
+/* MMSS Fabric */
+static struct msm8660_icc_node mmfab_mas_mdp_port0;
+static struct msm8660_icc_node mmfab_mas_mdp_port1;
+static struct msm8660_icc_node mmfab_mas_adm1_port0;
+static struct msm8660_icc_node mmfab_mas_rotator;
+static struct msm8660_icc_node mmfab_mas_graphics_3d;
+static struct msm8660_icc_node mmfab_mas_jpeg_dec;
+static struct msm8660_icc_node mmfab_mas_graphics_2d_core0;
+static struct msm8660_icc_node mmfab_mas_vfe;
+static struct msm8660_icc_node mmfab_mas_vpe;
+static struct msm8660_icc_node mmfab_mas_jpeg_enc;
+static struct msm8660_icc_node mmfab_mas_graphics_2d_core1;
+static struct msm8660_icc_node mmfab_mas_hd_codec_port0;
+static struct msm8660_icc_node mmfab_mas_hd_codec_port1;
+static struct msm8660_icc_node mmfab_to_appss;
+static struct msm8660_icc_node mmfab_slv_smi;
+static struct msm8660_icc_node mmfab_slv_mm_imem;
+
+/* Daytona Fabric (DFAB) */
+static struct msm8660_icc_node dfab_mas_sdc1;
+static struct msm8660_icc_node dfab_mas_sdc2;
+static struct msm8660_icc_node dfab_mas_sdc3;
+static struct msm8660_icc_node dfab_mas_sdc4;
+static struct msm8660_icc_node dfab_mas_sdc5;
+static struct msm8660_icc_node dfab_mas_adm0_master;
+static struct msm8660_icc_node dfab_mas_adm1_master;
+static struct msm8660_icc_node dfab_to_sfab;
+static struct msm8660_icc_node dfab_slv_sdc1;
+static struct msm8660_icc_node dfab_slv_sdc2;
+static struct msm8660_icc_node dfab_slv_sdc3;
+static struct msm8660_icc_node dfab_slv_sdc4;
+static struct msm8660_icc_node dfab_slv_sdc5;
+static struct msm8660_icc_node dfab_mas_usb_hs;
+static struct msm8660_icc_node dfab_mas_dsps;
+
+/*
+ * =========================================================================
+ * 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)
+ * =========================================================================
+ */
+static struct msm8660_icc_node mas_ampss_m0 = {
+ .name = "mas_ampss_m0",
+ .num_links = 3,
+ .buswidth = 8,
+ .mas_port = 0,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &slv_ebi_ch0, &afab_to_mmss, &afab_to_system },
+};
+
+static struct msm8660_icc_node mas_ampss_m1 = {
+ .name = "mas_ampss_m1",
+ .num_links = 3,
+ .buswidth = 8,
+ .mas_port = 1,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &slv_ebi_ch0, &afab_to_mmss, &afab_to_system },
+};
+
+static struct msm8660_icc_node slv_ebi_ch0 = {
+ .name = "slv_ebi_ch0",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 0,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node slv_ampss_l2 = {
+ .name = "slv_ampss_l2",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 1,
+ .mas_tier = 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.
+ */
+static struct msm8660_icc_node afab_to_mmss = {
+ .name = "afab_to_mmss",
+ .num_links = 2,
+ .buswidth = 8,
+ .mas_port = 2,
+ .slv_port = 2,
+ .mas_tier = ARB_TIER1,
+ .link_nodes = { &mmfab_to_appss, &slv_ebi_ch0 },
+};
+
+static struct msm8660_icc_node afab_to_system = {
+ .name = "afab_to_system",
+ .num_links = 2,
+ .buswidth = 8,
+ .mas_port = 3,
+ .slv_port = 3,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &sfab_to_appss, &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 legacy vendor kernel
+ * 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)
+ * =========================================================================
+ */
+static struct msm8660_icc_node sfab_mas_appss = {
+ .name = "sfab_mas_appss",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = 0,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &afab_to_system },
+};
+
+static struct msm8660_icc_node sfab_mas_sps = {
+ .name = "sfab_mas_sps",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = 1,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &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.
+ */
+static struct msm8660_icc_node sfab_mas_adm0_port0 = {
+ .name = "sfab_mas_adm0_port0",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = 2,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &sfab_to_appss },
+};
+
+static struct msm8660_icc_node sfab_mas_adm0_port1 = {
+ .name = "sfab_mas_adm0_port1",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = 3,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &sfab_to_appss },
+};
+
+static struct msm8660_icc_node sfab_mas_adm1_port0 = {
+ .name = "sfab_mas_adm1_port0",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = 4,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &sfab_to_appss },
+};
+
+static struct msm8660_icc_node sfab_mas_adm1_port1 = {
+ .name = "sfab_mas_adm1_port1",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = 5,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &sfab_to_appss },
+};
+
+static struct msm8660_icc_node sfab_mas_lpass_proc = {
+ .name = "sfab_mas_lpass_proc",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 6,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_mss_proci = {
+ .name = "sfab_mas_mss_proci",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 7,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_mss_procd = {
+ .name = "sfab_mas_mss_procd",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 8,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_mss_mdm_port0 = {
+ .name = "sfab_mas_mss_mdm_port0",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 9,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_lpass = {
+ .name = "sfab_mas_lpass",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 10,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_mmss_fpb = {
+ .name = "sfab_mas_mmss_fpb",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 13,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_adm1_ci = {
+ .name = "sfab_mas_adm1_ci",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 14,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_adm0_ci = {
+ .name = "sfab_mas_adm0_ci",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 15,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node sfab_mas_mss_mdm_port1 = {
+ .name = "sfab_mas_mss_mdm_port1",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 16,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+/* USB HS has no dedicated master port in legacy vendor kernel SFAB - bandwidth voting only */
+static struct msm8660_icc_node sfab_mas_usb_hs = {
+ .name = "sfab_mas_usb_hs",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &sfab_to_appss },
+};
+
+static struct msm8660_icc_node sfab_to_appss = {
+ .name = "sfab_to_appss",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 0,
+ .mas_tier = 0,
+ .link_nodes = { &afab_to_system },
+};
+
+static struct msm8660_icc_node sfab_to_system_fpb = {
+ .name = "sfab_to_system_fpb",
+ .num_links = 0,
+ .buswidth = 4,
+ .mas_port = -1,
+ .slv_port = 7,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_to_cpss_fpb = {
+ .name = "sfab_to_cpss_fpb",
+ .num_links = 0,
+ .buswidth = 4,
+ .mas_port = -1,
+ .slv_port = 6,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_slv_sps = {
+ .name = "sfab_slv_sps",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 1,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_slv_system_imem = {
+ .name = "sfab_slv_system_imem",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 2,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_slv_ampss = {
+ .name = "sfab_slv_ampss",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 3,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_slv_mss = {
+ .name = "sfab_slv_mss",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 4,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_slv_lpass = {
+ .name = "sfab_slv_lpass",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 5,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node sfab_slv_mmss_fpb = {
+ .name = "sfab_slv_mmss_fpb",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 8,
+ .mas_tier = 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 legacy vendor kernel SFAB config (DFAB is separate fabric).
+ */
+static struct msm8660_icc_node sfab_to_dfab = {
+ .name = "sfab_to_dfab",
+ .num_links = 2,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab, &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).
+ * =========================================================================
+ */
+static struct msm8660_icc_node mmfab_mas_mdp_port0 = {
+ .name = "mmfab_mas_mdp_port0",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 0,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER1,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_mdp_port1 = {
+ .name = "mmfab_mas_mdp_port1",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 1,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER1,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_adm1_port0 = {
+ .name = "mmfab_mas_adm1_port0",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = 2,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+};
+
+static struct msm8660_icc_node mmfab_mas_rotator = {
+ .name = "mmfab_mas_rotator",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 3,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_graphics_3d = {
+ .name = "mmfab_mas_graphics_3d",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 4,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_jpeg_dec = {
+ .name = "mmfab_mas_jpeg_dec",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 5,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_graphics_2d_core0 = {
+ .name = "mmfab_mas_graphics_2d_core0",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 6,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_vfe = {
+ .name = "mmfab_mas_vfe",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 7,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_vpe = {
+ .name = "mmfab_mas_vpe",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 8,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_jpeg_enc = {
+ .name = "mmfab_mas_jpeg_enc",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 9,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_graphics_2d_core1 = {
+ .name = "mmfab_mas_graphics_2d_core1",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 10,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_hd_codec_port0 = {
+ .name = "mmfab_mas_hd_codec_port0",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 12,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &mmfab_to_appss },
+};
+
+static struct msm8660_icc_node mmfab_mas_hd_codec_port1 = {
+ .name = "mmfab_mas_hd_codec_port1",
+ .num_links = 2,
+ .buswidth = 16,
+ .mas_port = 13,
+ .slv_port = -1,
+ .mas_tier = ARB_TIER2,
+ .link_nodes = { &mmfab_slv_smi, &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.
+ */
+static struct msm8660_icc_node mmfab_to_appss = {
+ .name = "mmfab_to_appss",
+ .num_links = 3,
+ .buswidth = 8,
+ .mas_port = 11,
+ .slv_port = 1,
+ .mas_tier = ARB_TIER1,
+ .link_nodes = { &afab_to_mmss, &mmfab_slv_smi, &mmfab_slv_mm_imem },
+};
+
+static struct msm8660_icc_node mmfab_slv_smi = {
+ .name = "mmfab_slv_smi",
+ .num_links = 0,
+ .buswidth = 16,
+ .mas_port = -1,
+ .slv_port = 0,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node mmfab_slv_mm_imem = {
+ .name = "mmfab_slv_mm_imem",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = 3,
+ .mas_tier = 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
+ * legacy vendor kernel clock voting mechanism.
+ * =========================================================================
+ */
+static struct msm8660_icc_node dfab_mas_sdc1 = {
+ .name = "dfab_mas_sdc1",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_mas_sdc2 = {
+ .name = "dfab_mas_sdc2",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_mas_sdc3 = {
+ .name = "dfab_mas_sdc3",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_mas_sdc4 = {
+ .name = "dfab_mas_sdc4",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_mas_sdc5 = {
+ .name = "dfab_mas_sdc5",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_mas_adm0_master = {
+ .name = "dfab_mas_adm0_master",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_mas_adm1_master = {
+ .name = "dfab_mas_adm1_master",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+static struct msm8660_icc_node dfab_to_sfab = {
+ .name = "dfab_to_sfab",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &sfab_to_dfab },
+};
+
+static struct msm8660_icc_node dfab_slv_sdc1 = {
+ .name = "dfab_slv_sdc1",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node dfab_slv_sdc2 = {
+ .name = "dfab_slv_sdc2",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node dfab_slv_sdc3 = {
+ .name = "dfab_slv_sdc3",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node dfab_slv_sdc4 = {
+ .name = "dfab_slv_sdc4",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+};
+
+static struct msm8660_icc_node dfab_slv_sdc5 = {
+ .name = "dfab_slv_sdc5",
+ .num_links = 0,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+};
+
+/* USB HS DFAB voter - keeps DFAB clock stable during USB activity */
+static struct msm8660_icc_node dfab_mas_usb_hs = {
+ .name = "dfab_mas_usb_hs",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &dfab_to_sfab },
+};
+
+/* DSPS DFAB voter - keeps DFAB clock stable during sensor activity */
+static struct msm8660_icc_node dfab_mas_dsps = {
+ .name = "dfab_mas_dsps",
+ .num_links = 1,
+ .buswidth = 8,
+ .mas_port = -1,
+ .slv_port = -1,
+ .mas_tier = 0,
+ .link_nodes = { &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 legacy vendor kernel 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. When the
+ * fabric has no master ports (arb_size == 0) the arb[0] access would
+ * read out of bounds; pad the lone bwsum into the low half of the
+ * word instead.
+ */
+ if (nslaves & 1) {
+ if (arb_size > 0) {
+ buf[index] = ((u32)arb[0] << 16) | bwsum[i];
+ i = 1;
+ } else {
+ buf[index] = bwsum[i];
+ i = 0;
+ }
+ index++;
+ } 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);
+}
+
+/*
+ * u64-safe replacement for icc_std_aggregate(): the standard helper sums
+ * average bandwidth into a u32, which can wrap around when summed across
+ * many high-bandwidth nodes. We accumulate in u64 internally and saturate
+ * back to U32_MAX on overflow rather than wrapping silently to a small
+ * value that would underclock the fabric.
+ */
+static int msm8660_icc_aggregate(struct icc_node *node, u32 tag,
+ u32 avg_bw, u32 peak_bw,
+ u32 *agg_avg, u32 *agg_peak)
+{
+ u64 new_avg = (u64)*agg_avg + avg_bw;
+
+ *agg_avg = (new_avg > U32_MAX) ? U32_MAX : (u32)new_avg;
+ *agg_peak = max(*agg_peak, peak_bw);
+ return 0;
+}
+
+static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+ struct msm8660_icc_provider *qp;
+ struct icc_provider *provider;
+ struct icc_node *n;
+ u64 rate = 0;
+ int ret, i;
+
+ provider = src->provider;
+ qp = to_msm8660_icc_provider(provider);
+
+ /*
+ * Per icc-rpm.c convention (qcom_icc_bus_aggregate): take the max
+ * per-node rate across the provider; do not sum, because the framework
+ * writes each path's bw to every node it traverses, so summing all
+ * provider nodes overcounts shared-node bandwidth. Taking the max
+ * gives the hottest single node, which is the correct fabric
+ * throughput requirement.
+ */
+ list_for_each_entry(n, &provider->nodes, node_list) {
+ u64 node_bw = max(icc_units_to_bps(n->avg_bw),
+ icc_units_to_bps(n->peak_bw));
+ u64 node_rate = div_u64(node_bw, qp->desc->bus_width);
+
+ rate = max(rate, node_rate);
+ }
+
+ /* Apply minimum floor to prevent bus starvation */
+ rate = max_t(u64, rate, MSM8660_FABRIC_MIN_RATE);
+ /*
+ * Cap at INT_MAX in u64 space; min_t(u32, ...) would cast the u64
+ * down to u32 BEFORE comparing, so a request above 4 GiB/s could
+ * wrap to a near-zero value below INT_MAX and pass through, then
+ * be programmed as a near-zero clock rate that effectively halts
+ * the interconnect fabric.
+ */
+ rate = min_t(u64, 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(%llu) error: %d\n",
+ qp->bus_clks[i].id, rate, ret);
+ /*
+ * Bail without updating qp->rate so the next
+ * icc_set call will retry the rate change
+ * rather than treating it as cached-applied.
+ */
+ return ret;
+ }
+ }
+ qp->rate = rate;
+ }
+
+ /* Send RPM fabric arbitration if available */
+ if (qp->rpm && qp->desc->rpm_resource >= 0) {
+ guard(mutex)(&qp->commit_lock);
+ 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;
+}
+
+/*
+ * devm cleanup paired with clk_bulk_prepare_enable() in probe. Registered
+ * via devm_add_action_or_reset() so any subsequent probe error path
+ * (including -EPROBE_DEFER from msm8660_get_rpm()) reliably releases the
+ * prepare/enable reference rather than leaking it across the retry.
+ */
+static void msm8660_icc_clk_release(void *data)
+{
+ struct msm8660_icc_provider *qp = data;
+
+ clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+}
+
+/*
+ * 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)
+ return dev_err_ptr_probe(dev, -EPROBE_DEFER,
+ "RPM device not found yet\n");
+
+ /*
+ * Pin the supplier BEFORE reading its drvdata. The device link
+ * (MANAGED, the default state) prevents the RPM driver from being
+ * unbound while we hold the link, which closes the window where a
+ * concurrent unbind+rebind could free the qcom_rpm pointer between
+ * dev_get_drvdata() and the link being established. If the link
+ * cannot be added (e.g. supplier is in the process of being
+ * removed) we defer and retry.
+ */
+ link = device_link_add(dev, &rpm_pdev->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER);
+ put_device(&rpm_pdev->dev);
+ if (!link)
+ return dev_err_ptr_probe(dev, -EPROBE_DEFER,
+ "failed to add device link to RPM\n");
+
+ /*
+ * Safe to read drvdata now: the device link pins the supplier so
+ * it cannot be unbound until our consumer (this interconnect
+ * provider) is unbound first.
+ *
+ * On the deferred-probe path below we don't (and must not) call
+ * device_link_remove(): the link is MANAGED with
+ * DL_FLAG_AUTOREMOVE_CONSUMER, so the driver core releases its
+ * reference automatically when our probe returns the error and the
+ * consumer device is unbound. A manual remove would drop an extra
+ * kref on the link kobject and risk a use-after-free.
+ */
+ rpm = dev_get_drvdata(&rpm_pdev->dev);
+ if (!rpm)
+ return dev_err_ptr_probe(dev, -EPROBE_DEFER,
+ "RPM not ready\n");
+
+ return rpm;
+}
+
+/*
+ * Drop cached icc_node * pointers stored in the static qnode templates.
+ * icc_nodes_remove() / icc_node_destroy() frees the icc_node memory, but
+ * because @qnodes is a const array of pointers into long-lived static
+ * storage, the ->node field outlives a probe failure and a subsequent
+ * unbind/rebind cycle. Without this clear, the next probe's
+ * "if (!qn->node) icc_node_create_dyn();" check sees the dangling cached
+ * pointer, skips re-allocation and hands the freed pointer back to the
+ * interconnect core -- use-after-free. icc_link_nodes() can also populate
+ * a target qnode's ->node mid-loop, so we always walk the full array.
+ */
+static void msm8660_clear_node_cache(struct msm8660_icc_node * const *qnodes,
+ size_t num_nodes)
+{
+ size_t i;
+
+ for (i = 0; i < num_nodes; i++)
+ if (qnodes[i])
+ qnodes[i]->node = NULL;
+}
+
+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;
+
+ ret = devm_mutex_init(dev, &qp->commit_lock);
+ if (ret)
+ return ret;
+
+ 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;
+
+ /*
+ * Per-clock -ENOENT is silently masked to a NULL handle by
+ * clk_bulk_get_optional(); clk_*() then no-op on those entries.
+ * The aggregate return is therefore only non-zero on real errors
+ * (most commonly -EPROBE_DEFER while the mmcc-msm8660 clock
+ * controller is still binding); propagate them.
+ */
+ ret = devm_clk_bulk_get_optional(dev, qp->num_clks, qp->bus_clks);
+ if (ret)
+ return ret;
+
+ 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;
+ } else {
+ /*
+ * Register the cleanup right after a successful
+ * prepare_enable so any later -EPROBE_DEFER or other
+ * probe error path (e.g. msm8660_get_rpm failing
+ * with -EPROBE_DEFER below) does not leak a clock
+ * prepare/enable reference across the retry.
+ */
+ ret = devm_add_action_or_reset(dev,
+ msm8660_icc_clk_release, qp);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /* 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,
+ 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 = msm8660_icc_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;
+
+ if (!qnodes[i]->node)
+ qnodes[i]->node = icc_node_create_dyn();
+ node = qnodes[i]->node;
+ if (IS_ERR(node)) {
+ ret = PTR_ERR(node);
+ goto err_remove_nodes;
+ }
+
+ ret = icc_node_set_name(node, provider, qnodes[i]->name);
+ if (ret) {
+ icc_node_destroy(node->id);
+ goto err_remove_nodes;
+ }
+
+ node->data = qnodes[i];
+ icc_node_add(node, provider);
+
+ for (j = 0; j < qnodes[i]->num_links; j++) {
+ ret = icc_link_nodes(node,
+ &qnodes[i]->link_nodes[j]->node);
+ if (ret)
+ goto err_remove_nodes;
+ }
+
+ 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);
+ msm8660_clear_node_cache(qnodes, num_nodes);
+ /*
+ * Do NOT call clk_bulk_disable_unprepare() here: the devm cleanup
+ * action registered after clk_bulk_prepare_enable() will run
+ * automatically when probe returns an error and devres unwinds.
+ * Calling it manually would double-unprepare and corrupt the
+ * clock-framework refcount.
+ */
+ return ret;
+}
+
+static void msm8660_icc_remove(struct platform_device *pdev)
+{
+ struct msm8660_icc_provider *qp = platform_get_drvdata(pdev);
+ const struct msm8660_icc_desc *desc = of_device_get_match_data(&pdev->dev);
+
+ icc_provider_deregister(&qp->provider);
+ icc_nodes_remove(&qp->provider);
+ if (desc)
+ msm8660_clear_node_cache(desc->nodes, desc->num_nodes);
+ /* clk cleanup happens via devm_add_action_or_reset on remove. */
+}
+
+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);
+
+/*
+ * No module_exit: Kconfig is bool, the driver is built-in only, and
+ * unbind/unload paths are not exercised. core_initcall + module_exit
+ * mix badly anyway (you cannot unload something registered earlier
+ * than module_init level).
+ */
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 interconnect driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/dt-bindings/interconnect/qcom,msm8660.h b/include/dt-bindings/interconnect/qcom,msm8660.h
index c9ce3f5a5276..bac9fc423da2 100644
--- a/include/dt-bindings/interconnect/qcom,msm8660.h
+++ b/include/dt-bindings/interconnect/qcom,msm8660.h
@@ -4,7 +4,7 @@
*
* Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
*
- * Based on webOS kernel msm_bus_board_8660.c
+ * Based on legacy vendor kernel msm_bus_board_8660.c
* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
*/
@@ -79,11 +79,11 @@
* 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,
+ * The legacy vendor 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
+ * voting mechanism. The legacy vendor 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.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC
2026-06-06 12:34 ` [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk
@ 2026-06-06 12:46 ` sashiko-bot
2026-06-06 13:23 ` Dmitry Baryshkov
1 sibling, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-06-06 12:46 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] The schema inherits a deprecation warning by referencing `qcom,rpm-common.yaml#` while explicitly using the deprecated `#interconnect-cells = <1>`.
--
commit 5ce31b27e0895bb0c743c163991a0db815e92b7e
Author: Herman van Hazendonk <github.com@herrie.org>
dt-bindings: interconnect: qcom: add msm8660 NoC
This commit adds a dt-binding schema and interconnect master/slave ID header
for the Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) Network-on-Chip.
The chip exposes four NoC fabrics modelled as interconnect providers which
program arbitration through RPM firmware.
> diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
> new file mode 100644
> index 0000000000000..ff28c7d46c323
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
[ ... ]
> +properties:
> + compatible:
> + enum:
> + - qcom,msm8660-apps-fabric
> + - qcom,msm8660-system-fabric
> + - qcom,msm8660-mmss-fabric
> + - qcom,msm8660-daytona-fabric
> +
> + '#interconnect-cells':
> + const: 1
> + description:
> + Each consumer phandle takes the form <provider node-id> where node-id
> + indexes the per-fabric ID array in
> + <dt-bindings/interconnect/qcom,msm8660.h>. qcom,rpm-common.yaml
> + permissively allows 2 cells (and 1 marked deprecated); pin this
> + provider to 1, which is what of_icc_xlate_onecell strictly expects.
[ ... ]
> +allOf:
> + - $ref: qcom,rpm-common.yaml#
[Severity: Low]
Does referencing qcom,rpm-common.yaml# here cause a deprecation warning to
be emitted during dt_binding_check?
Because this binding explicitly forces '#interconnect-cells: const: 1', and
qcom,rpm-common.yaml marks 1-cell as deprecated, wouldn't evaluating
any valid DT node for this provider trigger a warning?
Since qcom,rpm-common.yaml defines no other meaningful constraints (it
specifies additionalProperties: true), does referencing it here provide any
functional value, or would it be better to remove the reference to avoid
build warning pollution?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260606-submit-interconnect-msm8660-v3-0-f9da0158cdf8@herrie.org?part=1
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-06-06 12:34 ` [PATCH v3 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-06-06 12:48 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-06-06 12:48 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] Memory leak of dynamically allocated target nodes on probe failure.
- [High] Use-After-Free (UAF) of cross-fabric nodes upon driver unbind.
- [High] Bandwidth starvation due to incorrect node clock rate calculation.
--
commit 5d22619e7f59ed3d33c0640a610abb2e710a6a9c
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 NoC fabrics (APPSS, System, MMSS, Daytona) that connect masters
and slaves on these Scorpion-class SoCs.
> 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
[ ... ]
> @@ -1231,23 +1231,23 @@ static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
[ ... ]
> + list_for_each_entry(n, &provider->nodes, node_list) {
> + u64 node_bw = max(icc_units_to_bps(n->avg_bw),
> + icc_units_to_bps(n->peak_bw));
> + u64 node_rate = div_u64(node_bw, qp->desc->bus_width);
[Severity: High]
Does this calculation incorrectly starve narrow links by using the global
fabric bus width?
The clock rate is calculated by dividing the node's bandwidth by the overall
fabric bus width (qp->desc->bus_width). However, some peripheral nodes
(like sfab_to_system_fpb) have a narrower bus width (4 bytes) than the full
fabric (8 bytes).
Dividing a 4-byte node's bandwidth by 8 instead of its own width halves the
resulting requested clock frequency, mathematically guaranteeing these narrower
links will be starved of bandwidth.
Should this use ((struct msm8660_icc_node *)n->data)->buswidth instead?
[ ... ]
> @@ -1563,30 +1563,30 @@ static int msm8660_icc_probe(struct platform_device *pdev)
[ ... ]
> + for (j = 0; j < qnodes[i]->num_links; j++) {
> + ret = icc_link_nodes(node,
> + &qnodes[i]->link_nodes[j]->node);
> + if (ret)
> + goto err_remove_nodes;
> + }
> +
> + 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);
> + msm8660_clear_node_cache(qnodes, num_nodes);
[Severity: High]
Does this error path leak memory for target nodes that were allocated
during icc_link_nodes() but not yet added to the provider?
If probe fails (e.g., -ENOMEM in icc_node_set_name()), the error path calls
icc_nodes_remove(), which frees nodes that have already been fully initialized
and added to provider->nodes.
The driver then calls msm8660_clear_node_cache(), which zeroes all
qnodes[k]->node pointers. This destroys the only reference to any un-added
target nodes that were allocated mid-loop by icc_link_nodes(), resulting in a
memory leak.
[ ... ]
> @@ -1604,11 +1604,11 @@ static void msm8660_icc_remove(struct platform_device *pdev)
> +static void msm8660_icc_remove(struct platform_device *pdev)
> +{
> + struct msm8660_icc_provider *qp = platform_get_drvdata(pdev);
> + const struct msm8660_icc_desc *desc = of_device_get_match_data(&pdev->dev);
> +
> + icc_provider_deregister(&qp->provider);
> + icc_nodes_remove(&qp->provider);
[Severity: High]
Can this cause a use-after-free of cross-fabric nodes if a fabric is unbound
via sysfs?
The interconnect fabrics are registered as separate platform devices.
Cross-fabric links are established by storing a raw pointer to the target
node in the source node's links array.
Because there is no .suppress_bind_attrs = true, fabrics can be individually
unbound via sysfs. Unbinding a target fabric triggers icc_nodes_remove(),
which destroys and frees its nodes. The still-bound source fabric then retains
a dangling pointer to the freed node in its links array, which will be
dereferenced during path finding operations.
> + if (desc)
> + msm8660_clear_node_cache(desc->nodes, desc->num_nodes);
> + /* clk cleanup happens via devm_add_action_or_reset on remove. */
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260606-submit-interconnect-msm8660-v3-0-f9da0158cdf8@herrie.org?part=2
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC
2026-06-06 12:34 ` [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk
2026-06-06 12:46 ` sashiko-bot
@ 2026-06-06 13:23 ` Dmitry Baryshkov
2026-06-06 14:32 ` me
1 sibling, 1 reply; 7+ messages in thread
From: Dmitry Baryshkov @ 2026-06-06 13:23 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: Georgi Djakov, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, linux-arm-msm, linux-pm,
devicetree, linux-kernel
On Sat, Jun 06, 2026 at 02:34:50PM +0200, Herman van Hazendonk wrote:
> Add a dt-binding schema and an interconnect master/slave ID header for
> the MSM8x60 family (MSM8260/MSM8660/APQ8060) Network-on-Chip. The
> chip exposes four NoC 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)
>
> The schema covers all four compatible strings, per-fabric clock-name
> lists (bus / bus_a / ebi1 / ebi1_a for AFAB; bus / bus_a / smi /
> smi_a for MMFAB; bus / bus_a for SFAB and DFAB), the required
> qcom,rpm phandle through which the provider hands the arbitration
> buffer to RPM firmware, and #interconnect-cells = <1>.
>
> The ID header lists per-fabric master / slave / gateway indices
> derived from the legacy vendor msm_bus_board_8660.c enums,
> normalised to the upstream interconnect-framework naming convention.
>
> Assisted-by: Claude:claude-opus-4-7 Sashiko:claude-haiku-4-5
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
> .../bindings/interconnect/qcom,msm8660.yaml | 166 +++++++++++++++++++++
> include/dt-bindings/interconnect/qcom,msm8660.h | 156 +++++++++++++++++++
> 2 files changed, 322 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
> new file mode 100644
> index 000000000000..ff28c7d46c32
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
> @@ -0,0 +1,166 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/interconnect/qcom,msm8660.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm MSM8x60 family Network-On-Chip interconnect
> +
> +maintainers:
> + - Herman van Hazendonk <github.com@herrie.org>
> +
> +examples:
> + - |
> + #include <dt-bindings/clock/qcom,rpmcc.h>
> +
> + interconnect-afab {
> + compatible = "qcom,msm8660-apps-fabric";
> + clocks = <&rpmcc RPM_APPS_FABRIC_CLK>,
> + <&rpmcc RPM_APPS_FABRIC_A_CLK>,
> + <&rpmcc RPM_EBI1_CLK>,
> + <&rpmcc RPM_EBI1_A_CLK>;
> + clock-names = "bus", "bus_a", "ebi1", "ebi1_a";
> + qcom,rpm = <&rpm>;
> + #interconnect-cells = <1>;
> + };
> +
> + interconnect-sfab {
> + compatible = "qcom,msm8660-system-fabric";
> + clocks = <&rpmcc RPM_SYS_FABRIC_CLK>,
> + <&rpmcc RPM_SYS_FABRIC_A_CLK>;
> + clock-names = "bus", "bus_a";
> + qcom,rpm = <&rpm>;
> + #interconnect-cells = <1>;
> + };
> +
> + interconnect-mmfab {
> + compatible = "qcom,msm8660-mmss-fabric";
> + clocks = <&rpmcc RPM_MM_FABRIC_CLK>,
> + <&rpmcc RPM_MM_FABRIC_A_CLK>,
> + <&rpmcc RPM_SMI_CLK>,
> + <&rpmcc RPM_SMI_A_CLK>;
> + clock-names = "bus", "bus_a", "smi", "smi_a";
> + qcom,rpm = <&rpm>;
> + #interconnect-cells = <1>;
> + };
> +
> + interconnect-dfab {
> + compatible = "qcom,msm8660-daytona-fabric";
> + clocks = <&rpmcc RPM_DAYTONA_FABRIC_CLK>,
> + <&rpmcc RPM_DAYTONA_FABRIC_A_CLK>;
Looking at it, I don't think those clocks sould be exposed here. Please
follow what Konrad did some time ago for smd-rpm interconnects: add
direct voting to the interconnect driver, them drop them from the clocks
driver. Ping me if you need help finding corresponding patch series (as
there were few more tricks.
> + clock-names = "bus", "bus_a";
> + qcom,rpm = <&rpm>;
> + #interconnect-cells = <1>;
> + };
> +...
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC
2026-06-06 13:23 ` Dmitry Baryshkov
@ 2026-06-06 14:32 ` me
0 siblings, 0 replies; 7+ messages in thread
From: me @ 2026-06-06 14:32 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Herman van Hazendonk, Georgi Djakov, Bjorn Andersson,
Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
linux-arm-msm, linux-pm, devicetree, linux-kernel
On 2026-06-06 15:23, Dmitry Baryshkov wrote:
> On Sat, Jun 06, 2026 at 02:34:50PM +0200, Herman van Hazendonk wrote:
>> Add a dt-binding schema and an interconnect master/slave ID header for
>> the MSM8x60 family (MSM8260/MSM8660/APQ8060) Network-on-Chip. The
>> chip exposes four NoC 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)
>>
>> The schema covers all four compatible strings, per-fabric clock-name
>> lists (bus / bus_a / ebi1 / ebi1_a for AFAB; bus / bus_a / smi /
>> smi_a for MMFAB; bus / bus_a for SFAB and DFAB), the required
>> qcom,rpm phandle through which the provider hands the arbitration
>> buffer to RPM firmware, and #interconnect-cells = <1>.
>>
>> The ID header lists per-fabric master / slave / gateway indices
>> derived from the legacy vendor msm_bus_board_8660.c enums,
>> normalised to the upstream interconnect-framework naming convention.
>>
>> Assisted-by: Claude:claude-opus-4-7 Sashiko:claude-haiku-4-5
>> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
>> ---
>> .../bindings/interconnect/qcom,msm8660.yaml | 166
>> +++++++++++++++++++++
>> include/dt-bindings/interconnect/qcom,msm8660.h | 156
>> +++++++++++++++++++
>> 2 files changed, 322 insertions(+)
>>
>> diff --git
>> a/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
>> b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
>> new file mode 100644
>> index 000000000000..ff28c7d46c32
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
>> @@ -0,0 +1,166 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/interconnect/qcom,msm8660.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Qualcomm MSM8x60 family Network-On-Chip interconnect
>> +
>> +maintainers:
>> + - Herman van Hazendonk <github.com@herrie.org>
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/clock/qcom,rpmcc.h>
>> +
>> + interconnect-afab {
>> + compatible = "qcom,msm8660-apps-fabric";
>> + clocks = <&rpmcc RPM_APPS_FABRIC_CLK>,
>> + <&rpmcc RPM_APPS_FABRIC_A_CLK>,
>> + <&rpmcc RPM_EBI1_CLK>,
>> + <&rpmcc RPM_EBI1_A_CLK>;
>> + clock-names = "bus", "bus_a", "ebi1", "ebi1_a";
>> + qcom,rpm = <&rpm>;
>> + #interconnect-cells = <1>;
>> + };
>> +
>> + interconnect-sfab {
>> + compatible = "qcom,msm8660-system-fabric";
>> + clocks = <&rpmcc RPM_SYS_FABRIC_CLK>,
>> + <&rpmcc RPM_SYS_FABRIC_A_CLK>;
>> + clock-names = "bus", "bus_a";
>> + qcom,rpm = <&rpm>;
>> + #interconnect-cells = <1>;
>> + };
>> +
>> + interconnect-mmfab {
>> + compatible = "qcom,msm8660-mmss-fabric";
>> + clocks = <&rpmcc RPM_MM_FABRIC_CLK>,
>> + <&rpmcc RPM_MM_FABRIC_A_CLK>,
>> + <&rpmcc RPM_SMI_CLK>,
>> + <&rpmcc RPM_SMI_A_CLK>;
>> + clock-names = "bus", "bus_a", "smi", "smi_a";
>> + qcom,rpm = <&rpm>;
>> + #interconnect-cells = <1>;
>> + };
>> +
>> + interconnect-dfab {
>> + compatible = "qcom,msm8660-daytona-fabric";
>> + clocks = <&rpmcc RPM_DAYTONA_FABRIC_CLK>,
>> + <&rpmcc RPM_DAYTONA_FABRIC_A_CLK>;
>
> Looking at it, I don't think those clocks sould be exposed here. Please
> follow what Konrad did some time ago for smd-rpm interconnects: add
> direct voting to the interconnect driver, them drop them from the
> clocks
> driver. Ping me if you need help finding corresponding patch series (as
> there were few more tricks.
Feel free to send it. This is my first work on kernel ever and to be
fair a lot of it comes from piggy backing on previous work, legacy
kernels and insights from Claude & Gemini.
I can say it "works" for my limited tests, but I haven't run a full
fledged OS yet to stress test most things.
HP TouchPad boots, audio works (though no Q6 (yet)), graphics works
(Adreno 220), camera works (camss & vfe31), video plays via vidc,
rotator, vfe works, sensors work (ALS, magnetometer, gyro), leds work,
usb works, touchscreen works. Only thing I'm struggling with is wifi &
bluetooth for the moment. It could very well be that when I rework
things "properly" my wifi and bluetooth issues disappear and start to
work. This old silicon is very unforgiving when it comes to bits set the
wrong way.
So any guidance is much appreciated :)
As always there are multiple roads that lead to Rome :)
Thanks,
Herman
>
>> + clock-names = "bus", "bus_a";
>> + qcom,rpm = <&rpm>;
>> + #interconnect-cells = <1>;
>> + };
>> +...
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-06-06 14:32 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-06 12:34 [PATCH v3 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-06-06 12:34 ` [PATCH v3 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk
2026-06-06 12:46 ` sashiko-bot
2026-06-06 13:23 ` Dmitry Baryshkov
2026-06-06 14:32 ` me
2026-06-06 12:34 ` [PATCH v3 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-06-06 12:48 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox