* [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver
@ 2026-06-04 18:43 Herman van Hazendonk
2026-06-04 18:43 ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk
2026-06-04 18:44 ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
0 siblings, 2 replies; 4+ messages in thread
From: Herman van Hazendonk @ 2026-06-04 18:43 UTC (permalink / raw)
To: djakov
Cc: dmitry.baryshkov, konrad.dybcio, odelu.kukatla,
raviteja.laggyshetty, luca.weiss, abel.vesa, jie.gan, robh,
krzk+dt, conor+dt, linux-arm-msm, linux-pm, devicetree,
linux-kernel, Herman van Hazendonk
v2 addresses Jie's, Sashiko's and Dmitry's review of v1:
Binding (PATCH 1/2):
- Add the dt-binding schema (qcom,msm8660.yaml). v1 only shipped
the ID header (Jie flagged the missing schema). The schema
covers all four compatibles, 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) with descriptions for each clock
(Dmitry), the required qcom,rpm phandle, and
#interconnect-cells = <1>.
- Drop the SFPB/CFPB mention from the header commit message;
those fabrics aren't modelled by the driver (Sashiko Low).
Driver (PATCH 2/2):
- Fix the bandwidth aggregation in msm8660_icc_set() (Sashiko
High). v1 walked provider->nodes and re-summed n->avg_bw
across all nodes, which double-counts: the framework already
writes each path's bw to every node it traverses, so summing
here gives e.g. master_bw + slave_bw for the same flow. v2
takes the max per-node rate across the provider (matches the
qcom_icc_bus_aggregate() convention in icc-rpm.c).
- Switch to dynamic ICC node IDs via icc_node_create_dyn() and
pointer-based linking via icc_link_nodes(), matching the
icc-rpmh-style pattern (Dmitry). Drop the internal MSM8660_*
enum entirely; struct msm8660_icc_node now carries a struct
icc_node *node and a flexible link_nodes[] array of qnode
pointers.
- Expand the DEFINE_QNODE macro (Dmitry); each of the 63 qnodes
is now an explicit static struct definition, with forward
declarations grouped at the top.
- Use dev_err_ptr_probe() in msm8660_get_rpm()'s deferred-probe
paths (Dmitry).
- Limit the clock-bulk-get fallback to -ENOENT only (Dmitry);
propagate every other error including -EPROBE_DEFER instead
of "any non-DEFER continues without clock scaling".
- Kconfig: depend on MFD_QCOM_RPM=y so a built-in interconnect
provider can't link against a modular RPM (Sashiko Low).
- struct msm8660_icc_node: const char *name (Sashiko Low).
On-device validation (HP TouchPad / APQ8060):
All four fabric providers probe cleanly:
qnoc-msm8660 soc:interconnect@0: RPM fabric ARB enabled
(4 masters, 4 slaves, 2 tiered) [AFAB]
qnoc-msm8660 soc:interconnect@1: RPM fabric ARB enabled
(17 masters, 9 slaves, 2 tiered) [SFAB]
qnoc-msm8660 soc:interconnect@2: RPM fabric ARB enabled
(14 masters, 4 slaves, 3 tiered) [MMFAB]
qnoc-msm8660 soc:interconnect@3: MSM8660 interconnect provider
registered [DFAB]
mmcc-msm8660: MMSS fabric: unhalted all master ports (0-13)
msm_hsusb 12500000.usb: USB HS: Setting interconnect
bandwidth avg=61440 peak=61440 kBps
interconnect_summary shows the expected per-node aggregation
(e.g. slv_ebi_ch0 sums MMC + MDP + DMA + USB consumer requests)
and msm8660_icc_set() then derives a single fabric clock rate
from the per-node max rather than the per-node sum (the v1
double-count is gone in v2).
The companion DTS patches that enable the providers for the
HP TouchPad will be sent separately to the ARM/DTS tree.
Herman van Hazendonk (2):
dt-bindings: interconnect: qcom: add msm8660 NoC
interconnect: qcom: add MSM8x60 NoC driver
.../bindings/interconnect/qcom,msm8660.yaml | 157 ++
drivers/interconnect/qcom/Kconfig | 14 +
drivers/interconnect/qcom/Makefile | 2 +
drivers/interconnect/qcom/msm8660.c | 1619 +++++++++++++++++
.../dt-bindings/interconnect/qcom,msm8660.h | 156 ++
5 files changed, 1948 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml
create mode 100644 drivers/interconnect/qcom/msm8660.c
create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h
base-commit: 944125b4c454b58d2fe6e35f1087a932b2050dff
--
2.43.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC 2026-06-04 18:43 [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk @ 2026-06-04 18:43 ` Herman van Hazendonk 2026-06-04 18:44 ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk 1 sibling, 0 replies; 4+ messages in thread From: Herman van Hazendonk @ 2026-06-04 18:43 UTC (permalink / raw) To: djakov Cc: dmitry.baryshkov, konrad.dybcio, odelu.kukatla, raviteja.laggyshetty, luca.weiss, abel.vesa, jie.gan, robh, krzk+dt, conor+dt, 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. Signed-off-by: Herman van Hazendonk <github.com@herrie.org> --- .../bindings/interconnect/qcom,msm8660.yaml | 157 ++++++++++++++++++ .../dt-bindings/interconnect/qcom,msm8660.h | 156 +++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h diff --git a/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml new file mode 100644 index 000000000000..f8b3a05d5ba1 --- /dev/null +++ b/Documentation/devicetree/bindings/interconnect/qcom,msm8660.yaml @@ -0,0 +1,157 @@ +# 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 + + 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] 4+ messages in thread
* [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver 2026-06-04 18:43 [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk 2026-06-04 18:43 ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk @ 2026-06-04 18:44 ` Herman van Hazendonk 1 sibling, 0 replies; 4+ messages in thread From: Herman van Hazendonk @ 2026-06-04 18:44 UTC (permalink / raw) To: djakov Cc: dmitry.baryshkov, konrad.dybcio, odelu.kukatla, raviteja.laggyshetty, luca.weiss, abel.vesa, jie.gan, robh, krzk+dt, conor+dt, 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. 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 | 1619 +++++++++++++++++ .../dt-bindings/interconnect/qcom,msm8660.h | 6 +- 4 files changed, 1638 insertions(+), 3 deletions(-) create mode 100644 drivers/interconnect/qcom/msm8660.c 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..6e55ff8e5b0e --- /dev/null +++ b/drivers/interconnect/qcom/msm8660.c @@ -0,0 +1,1619 @@ +// 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/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> + +#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) + */ +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; +}; + +/* + * 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) + 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. + */ + rpm = dev_get_drvdata(&rpm_pdev->dev); + if (!rpm) { + device_link_remove(dev, &rpm_pdev->dev); + return dev_err_ptr_probe(dev, -EPROBE_DEFER, + "RPM not ready\n"); + } + + 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. Only swallow -ENOENT (the clock provider exists but has + * no matching entry); propagate every other error including + * -EPROBE_DEFER (the provider exists but has not finished probing). + */ + ret = devm_clk_bulk_get_optional(dev, qp->num_clks, qp->bus_clks); + if (ret == -ENOENT) { + dev_warn(dev, "bus clocks not registered, continuing without clock scaling\n"); + qp->num_clks = 0; + } else 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++) + icc_link_nodes(node, &qnodes[i]->link_nodes[j]->node); + + 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); + /* + * 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); + + icc_provider_deregister(&qp->provider); + icc_nodes_remove(&qp->provider); + /* 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] 4+ messages in thread
* [PATCH 0/2] interconnect: qcom: add MSM8x60 NoC driver @ 2026-05-30 14:00 Herman van Hazendonk 2026-05-31 4:09 ` [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver Herman van Hazendonk 0 siblings, 1 reply; 4+ 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] 4+ messages in thread
* [PATCH v2 0/3] thermal: qcom: add PM8901 PMIC temperature-alarm driver 2026-05-30 14:00 [PATCH 0/2] " Herman van Hazendonk @ 2026-05-31 4:09 ` Herman van Hazendonk 2026-05-31 4:09 ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk 0 siblings, 1 reply; 4+ 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] 4+ 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 ` Herman van Hazendonk 0 siblings, 0 replies; 4+ 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] 4+ messages in thread
end of thread, other threads:[~2026-06-04 18:44 UTC | newest] Thread overview: 4+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-04 18:43 [PATCH v2 0/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk 2026-06-04 18:43 ` [PATCH v2 1/2] dt-bindings: interconnect: qcom: add msm8660 NoC Herman van Hazendonk 2026-06-04 18:44 ` [PATCH v2 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk -- strict thread matches above, loose matches on Subject: below -- 2026-05-30 14:00 [PATCH 0/2] " Herman van Hazendonk 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 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox