* [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 " Herman van Hazendonk
@ 2026-05-30 14:00 ` Herman van Hazendonk
0 siblings, 0 replies; 6+ messages in thread
From: Herman van Hazendonk @ 2026-05-30 14:00 UTC (permalink / raw)
To: Conor Dooley, devicetree, Georgi Djakov, Krzysztof Kozlowski,
linux-arm-msm, linux-kernel, linux-pm, Rob Herring
Add a Qualcomm interconnect driver for the MSM8x60 family
(MSM8260/MSM8660/APQ8060), modelling the four fabrics that connect
masters and slaves on these Scorpion-class SoCs:
- AFAB : Application/CPU fabric
- SFAB : System fabric (peripherals, USB, SDCC, etc.)
- MMFAB : Multimedia fabric (MDP, VFE, VIDC, GPU, JPEG, VPE, ROT)
- DFAB : Daytona fabric (low-bandwidth peripherals)
The driver implements the interconnect-provider API so that consumer
drivers (display, camera, video, GPU, USB, MMC) can request bandwidth
between specific masters and slaves via icc_set_bw(), letting the
firmware-managed bus-scaling decide actual NoC clock rates.
Used on the HP TouchPad (Tenderloin) and on the Pre3 / Veer
form-factors; without it, the multimedia and storage paths are
starved of bandwidth and run at minimum NoC clocks.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/interconnect/qcom/Kconfig | 10 +
drivers/interconnect/qcom/Makefile | 2 +
drivers/interconnect/qcom/msm8660.c | 1008 +++++++++++++++++++++++++++
3 files changed, 1020 insertions(+)
create mode 100644 drivers/interconnect/qcom/msm8660.c
diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig
index 786b4eda44b4..17364be522c7 100644
--- a/drivers/interconnect/qcom/Kconfig
+++ b/drivers/interconnect/qcom/Kconfig
@@ -80,6 +80,16 @@ config INTERCONNECT_QCOM_MSM8953
This is a driver for the Qualcomm Network-on-Chip on msm8953-based
platforms.
+config INTERCONNECT_QCOM_MSM8660
+ tristate "Qualcomm MSM8x60 interconnect driver"
+ depends on INTERCONNECT_QCOM
+ depends on MFD_QCOM_RPM
+ help
+ This is a driver for the Qualcomm fabric-based bus interconnect
+ on MSM8x60 family (MSM8260/MSM8660/APQ8060) platforms (e.g., HP TouchPad).
+ The driver manages APPSS, System, and MMSS fabrics and sends
+ per-port bandwidth arbitration requests to RPM firmware.
+
config INTERCONNECT_QCOM_MSM8974
tristate "Qualcomm MSM8974 interconnect driver"
depends on INTERCONNECT_QCOM
diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile
index cdf2c6c9fbf3..0c849c30a907 100644
--- a/drivers/interconnect/qcom/Makefile
+++ b/drivers/interconnect/qcom/Makefile
@@ -13,6 +13,7 @@ qnoc-msm8916-objs := msm8916.o
qnoc-msm8937-objs := msm8937.o
qnoc-msm8939-objs := msm8939.o
qnoc-msm8953-objs := msm8953.o
+qnoc-msm8660-objs := msm8660.o
qnoc-msm8974-objs := msm8974.o
qnoc-msm8976-objs := msm8976.o
qnoc-msm8996-objs := msm8996.o
@@ -58,6 +59,7 @@ obj-$(CONFIG_INTERCONNECT_QCOM_MSM8916) += qnoc-msm8916.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8937) += qnoc-msm8937.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8939) += qnoc-msm8939.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8953) += qnoc-msm8953.o
+obj-$(CONFIG_INTERCONNECT_QCOM_MSM8660) += qnoc-msm8660.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8974) += qnoc-msm8974.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8976) += qnoc-msm8976.o
obj-$(CONFIG_INTERCONNECT_QCOM_MSM8996) += qnoc-msm8996.o
diff --git a/drivers/interconnect/qcom/msm8660.c b/drivers/interconnect/qcom/msm8660.c
new file mode 100644
index 000000000000..eab5b928f681
--- /dev/null
+++ b/drivers/interconnect/qcom/msm8660.c
@@ -0,0 +1,1008 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) interconnect driver
+ *
+-2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Based on msm8974.c by Brian Masney <masneyb@onstation.org>
+ * and webOS kernel msm_bus_board_8660.c / msm_bus_fabric.c
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * MSM8x60 has a fabric-based bus architecture:
+ *
+ * +------------------+
+ * | APPSS Fabric | (CPU, L2, Memory)
+ * +--------+---------+
+ * |
+ * +-------------+-------------+
+ * | |
+ * +------+------+ +-------+-------+
+ * | MMSS Fabric | | System Fabric |
+ * | (Display, | | (Peripherals, |
+ * | Camera, | | DMA, etc) |
+ * | Video) | +-------+-------+
+ * +-------------+ |
+ * +---------+---------+
+ * | |
+ * +------+------+ +------+------+
+ * | System FPB | | CPSS FPB |
+ * | (RPM, PMIC) | | (GSBI, USB) |
+ * +-------------+ +-------------+
+ *
+ * Each fabric has an RPM arbitration interface that programs per-port
+ * bandwidth and priority tier via MM_FABRIC_ARB / SYS_FABRIC_ARB /
+ * APPS_FABRIC_ARB registers. The webOS kernel sent these as packed
+ * u16 arrays (bwsum + arb) through msm_rpm_set(). This driver uses
+ * the mainline qcom_rpm_write() interface to do the same.
+ */
+
+#include <dt-bindings/interconnect/qcom,msm8660.h>
+#include <dt-bindings/mfd/qcom-rpm.h>
+
+#include <linux/args.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interconnect-provider.h>
+#include <linux/io.h>
+#include <linux/mfd/qcom_rpm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* Internal node IDs - these map to the DT binding IDs plus fabric offset */
+enum {
+ /* APPSS Fabric nodes */
+ MSM8660_AFAB_MAS_AMPSS_M0 = 1,
+ MSM8660_AFAB_MAS_AMPSS_M1,
+ MSM8660_AFAB_SLV_EBI_CH0,
+ MSM8660_AFAB_SLV_AMPSS_L2,
+ MSM8660_AFAB_TO_MMSS,
+ MSM8660_AFAB_TO_SYSTEM,
+
+ /* System Fabric nodes */
+ MSM8660_SFAB_MAS_APPSS,
+ MSM8660_SFAB_MAS_SPS,
+ MSM8660_SFAB_MAS_ADM0_PORT0,
+ MSM8660_SFAB_MAS_ADM0_PORT1,
+ MSM8660_SFAB_MAS_ADM1_PORT0,
+ MSM8660_SFAB_MAS_ADM1_PORT1,
+ MSM8660_SFAB_MAS_LPASS_PROC,
+ MSM8660_SFAB_MAS_MSS_PROCI,
+ MSM8660_SFAB_MAS_MSS_PROCD,
+ MSM8660_SFAB_MAS_MSS_MDM_PORT0,
+ MSM8660_SFAB_MAS_LPASS,
+ MSM8660_SFAB_MAS_MMSS_FPB,
+ MSM8660_SFAB_MAS_ADM1_CI,
+ MSM8660_SFAB_MAS_ADM0_CI,
+ MSM8660_SFAB_MAS_MSS_MDM_PORT1,
+ MSM8660_SFAB_MAS_USB_HS,
+ MSM8660_SFAB_TO_APPSS,
+ MSM8660_SFAB_TO_SYSTEM_FPB,
+ MSM8660_SFAB_TO_CPSS_FPB,
+ MSM8660_SFAB_SLV_SPS,
+ MSM8660_SFAB_SLV_SYSTEM_IMEM,
+ MSM8660_SFAB_SLV_AMPSS,
+ MSM8660_SFAB_SLV_MSS,
+ MSM8660_SFAB_SLV_LPASS,
+ MSM8660_SFAB_SLV_MMSS_FPB,
+ MSM8660_SFAB_TO_DFAB,
+
+ /* Daytona Fabric nodes (DFAB) - peripheral bus */
+ MSM8660_DFAB_MAS_SDC1,
+ MSM8660_DFAB_MAS_SDC2,
+ MSM8660_DFAB_MAS_SDC3,
+ MSM8660_DFAB_MAS_SDC4,
+ MSM8660_DFAB_MAS_SDC5,
+ MSM8660_DFAB_MAS_ADM0_MASTER,
+ MSM8660_DFAB_MAS_ADM1_MASTER,
+ MSM8660_DFAB_TO_SFAB,
+ MSM8660_DFAB_SLV_SDC1,
+ MSM8660_DFAB_SLV_SDC2,
+ MSM8660_DFAB_SLV_SDC3,
+ MSM8660_DFAB_SLV_SDC4,
+ MSM8660_DFAB_SLV_SDC5,
+ MSM8660_DFAB_MAS_USB_HS, /* USB HS DFAB voter */
+ MSM8660_DFAB_MAS_DSPS, /* DSPS DFAB voter */
+
+ /* MMSS Fabric nodes */
+ MSM8660_MMFAB_MAS_MDP_PORT0,
+ MSM8660_MMFAB_MAS_MDP_PORT1,
+ MSM8660_MMFAB_MAS_ADM1_PORT0,
+ MSM8660_MMFAB_MAS_ROTATOR,
+ MSM8660_MMFAB_MAS_GRAPHICS_3D,
+ MSM8660_MMFAB_MAS_JPEG_DEC,
+ MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE0,
+ MSM8660_MMFAB_MAS_VFE,
+ MSM8660_MMFAB_MAS_VPE,
+ MSM8660_MMFAB_MAS_JPEG_ENC,
+ MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE1,
+ MSM8660_MMFAB_MAS_HD_CODEC_PORT0,
+ MSM8660_MMFAB_MAS_HD_CODEC_PORT1,
+ MSM8660_MMFAB_TO_APPSS,
+ MSM8660_MMFAB_SLV_SMI,
+ MSM8660_MMFAB_SLV_MM_IMEM,
+};
+
+#define to_msm8660_icc_provider(_provider) \
+ container_of(_provider, struct msm8660_icc_provider, provider)
+
+/*
+ * Minimum fabric clock rate to prevent bus starvation.
+ *
+ * When no consumers request bandwidth, the rate calculation yields 0,
+ * causing fabric clocks to drop to minimum. This creates bimodal
+ * performance: fast when other subsystems (like display) happen to
+ * request bandwidth, slow otherwise.
+ *
+ * 384 MHz keeps fabric fast during concurrent MDP display scanout
+ * and USB gadget traffic. webOS docs: "AXI bus frequency needs to be
+ * kept at maximum value while USB data transfers are happening."
+ * 266 MHz was insufficient - USB crashed during display activity.
+ */
+#define MSM8660_FABRIC_MIN_RATE 384000000UL /* 384 MHz */
+
+/*
+ * Maximum RPM ARB buffer size across all fabrics.
+ * MM fabric is largest at 23 u32 words.
+ */
+#define MSM8660_MAX_RPM_BUF 23
+
+/*
+ * RPM fabric arbitration data format (from webOS msm_bus_fabric.c):
+ *
+ * Each u16 arb entry: bit 15 = tier (1=TIER1 high priority), bits 14-0 = BW
+ * Bandwidth is in 128KB units (bytes >> 17).
+ * Two u16 values are packed into each u32 RPM register word.
+ *
+ * Buffer layout: [bwsum pairs] [arb pairs]
+ * bwsum[slave_port] = total bandwidth to that slave
+ * arb[(tier-1)*nmasters + master_port] = per-master arbitration entry
+ */
+#define ARB_BWMASK 0x7FFF
+#define ARB_TIERMASK 0x8000
+#define ARB_TIER1 1
+#define ARB_TIER2 2
+
+static const struct clk_bulk_data msm8660_afab_clocks[] = {
+ { .id = "bus" },
+ { .id = "bus_a" },
+ { .id = "ebi1" },
+ { .id = "ebi1_a" },
+};
+
+static const struct clk_bulk_data msm8660_sfab_clocks[] = {
+ { .id = "bus" },
+ { .id = "bus_a" },
+};
+
+static const struct clk_bulk_data msm8660_mmfab_clocks[] = {
+ { .id = "bus" },
+ { .id = "bus_a" },
+ { .id = "smi" },
+ { .id = "smi_a" },
+};
+
+static const struct clk_bulk_data msm8660_dfab_clocks[] = {
+ { .id = "bus" },
+ { .id = "bus_a" },
+};
+
+/**
+ * struct msm8660_icc_node - MSM8660 specific interconnect nodes
+ * @name: the node name used in debugfs
+ * @id: a unique node identifier
+ * @links: an array of nodes where we can go next while traversing
+ * @num_links: the total number of @links
+ * @buswidth: width of the interconnect between a node and the bus (bytes)
+ * @rate: current bus clock rate in Hz
+ * @mas_port: master port index for RPM ARB (-1 if not a master)
+ * @slv_port: slave port index for RPM bwsum (-1 if not a slave)
+ * @mas_tier: master priority tier (ARB_TIER1 or ARB_TIER2, 0 if N/A)
+ */
+struct msm8660_icc_node {
+ unsigned char *name;
+ u16 id;
+#define MSM8660_ICC_MAX_LINKS 3
+ u16 links[MSM8660_ICC_MAX_LINKS];
+ u16 num_links;
+ u16 buswidth;
+ u64 rate;
+ s8 mas_port;
+ s8 slv_port;
+ u8 mas_tier;
+};
+
+/**
+ * struct msm8660_icc_desc - Fabric descriptor
+ * @nodes: array of node pointers
+ * @num_nodes: number of nodes
+ * @bus_clks: clock definitions
+ * @num_clks: number of clocks
+ * @rpm_resource: QCOM_RPM_*_FABRIC_ARB constant, or -1 for no ARB
+ * @nmasters: number of master ports in this fabric (for ARB array sizing)
+ * @nslaves: number of slave ports in this fabric (for bwsum array sizing)
+ * @ntieredslaves: number of tiered slaves (ARB rows)
+ * @default_tiered_slave: 1-based index of default tiered slave for masters
+ * @rpm_buf_size: number of u32 words for RPM write
+ */
+struct msm8660_icc_desc {
+ struct msm8660_icc_node * const *nodes;
+ size_t num_nodes;
+ const struct clk_bulk_data *bus_clks;
+ size_t num_clks;
+ int rpm_resource;
+ u8 nmasters;
+ u8 nslaves;
+ u8 ntieredslaves;
+ u8 default_tiered_slave;
+ u8 rpm_buf_size;
+};
+
+/**
+ * struct msm8660_icc_provider - MSM8660 specific interconnect provider
+ * @provider: generic interconnect provider
+ * @bus_clks: the clk_bulk_data table of bus clocks
+ * @num_clks: the total number of clk_bulk_data entries
+ * @rpm: RPM handle for fabric arbitration writes
+ * @desc: fabric descriptor with RPM metadata
+ * @arb: pre-allocated arbitration array (nmasters * ntieredslaves u16 entries)
+ * @bwsum: pre-allocated bandwidth sum array (nslaves u16 entries)
+ * @rpm_buf: pre-allocated RPM write buffer (rpm_buf_size u32 entries)
+ */
+struct msm8660_icc_provider {
+ struct icc_provider provider;
+ struct clk_bulk_data *bus_clks;
+ int num_clks;
+ struct qcom_rpm *rpm;
+ const struct msm8660_icc_desc *desc;
+ u16 *arb;
+ u16 *bwsum;
+ u32 *rpm_buf;
+};
+
+/*
+ * Node definitions with RPM port mapping.
+ *
+ * DEFINE_QNODE(_name, _id, _buswidth, _mas_port, _slv_port, _tier, links...)
+ *
+ * _mas_port: master port index for RPM ARB array (-1 if not a master)
+ * _slv_port: slave port index for RPM bwsum array (-1 if not a slave)
+ * _tier: master priority tier (ARB_TIER1=1, ARB_TIER2=2, 0 if N/A)
+ */
+#define DEFINE_QNODE(_name, _id, _buswidth, _mas, _slv, _tier, ...) \
+ static struct msm8660_icc_node _name = { \
+ .name = #_name, \
+ .id = _id, \
+ .buswidth = _buswidth, \
+ .num_links = COUNT_ARGS(__VA_ARGS__), \
+ .links = { __VA_ARGS__ }, \
+ .mas_port = _mas, \
+ .slv_port = _slv, \
+ .mas_tier = _tier, \
+ }
+
+/*
+ * =========================================================================
+ * APPSS Fabric nodes
+ *
+ * 4 masters, 4 slaves, 2 tiered slaves
+ * Master ports: SMPSS_M0=0, SMPSS_M1=1, FAB_MMSS=2, FAB_SYSTEM=3
+ * Slave ports: EBI_CH0=0, SMPSS_L2=1, MMSS_FAB=2, SYSTEM_FAB=3
+ * Default target: tiered slave 1 (EBI_CH0)
+ * =========================================================================
+ */
+DEFINE_QNODE(mas_ampss_m0, MSM8660_AFAB_MAS_AMPSS_M0, 8, 0, -1, ARB_TIER2,
+ MSM8660_AFAB_SLV_EBI_CH0, MSM8660_AFAB_TO_MMSS, MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(mas_ampss_m1, MSM8660_AFAB_MAS_AMPSS_M1, 8, 1, -1, ARB_TIER2,
+ MSM8660_AFAB_SLV_EBI_CH0, MSM8660_AFAB_TO_MMSS, MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(slv_ebi_ch0, MSM8660_AFAB_SLV_EBI_CH0, 8, -1, 0, 0);
+DEFINE_QNODE(slv_ampss_l2, MSM8660_AFAB_SLV_AMPSS_L2, 8, -1, 1, 0);
+/*
+ * Gateway nodes need links to both the cross-fabric gateway AND the memory
+ * slave to enable cross-fabric paths. Without link to EBI_CH0, path_find()
+ * can't route from MMSS/System fabric masters to main memory.
+ *
+ * AFAB_TO_MMSS doubles as AFAB master port 2 (the FAB_MMSS master). MDP
+ * scanout and GPU traffic enter AFAB through this gateway. Mark it
+ * ARB_TIER1 so display/multimedia traffic keeps priority over CPU L2
+ * misses inside the APPSS fabric — without this, MDP TIER1 priority
+ * earned in MMFAB is dropped at the AFAB boundary and MDP fetches lose
+ * arbitration to CPU traffic, producing PRIMARY_INTF_UDERRUN.
+ */
+DEFINE_QNODE(afab_to_mmss, MSM8660_AFAB_TO_MMSS, 8, 2, 2, ARB_TIER1,
+ MSM8660_MMFAB_TO_APPSS, MSM8660_AFAB_SLV_EBI_CH0);
+DEFINE_QNODE(afab_to_system, MSM8660_AFAB_TO_SYSTEM, 8, 3, 3, ARB_TIER2,
+ MSM8660_SFAB_TO_APPSS, MSM8660_AFAB_SLV_EBI_CH0);
+
+static struct msm8660_icc_node * const msm8660_afab_nodes[] = {
+ [AFAB_MAS_AMPSS_M0] = &mas_ampss_m0,
+ [AFAB_MAS_AMPSS_M1] = &mas_ampss_m1,
+ [AFAB_SLV_EBI_CH0] = &slv_ebi_ch0,
+ [AFAB_SLV_AMPSS_L2] = &slv_ampss_l2,
+ [AFAB_TO_MMSS] = &afab_to_mmss,
+ [AFAB_TO_SYSTEM] = &afab_to_system,
+};
+
+static const struct msm8660_icc_desc msm8660_afab = {
+ .nodes = msm8660_afab_nodes,
+ .num_nodes = ARRAY_SIZE(msm8660_afab_nodes),
+ .bus_clks = msm8660_afab_clocks,
+ .num_clks = ARRAY_SIZE(msm8660_afab_clocks),
+ .rpm_resource = QCOM_RPM_APPS_FABRIC_ARB,
+ .nmasters = 4,
+ .nslaves = 4,
+ .ntieredslaves = 2,
+ .default_tiered_slave = 1, /* EBI_CH0 */
+ .rpm_buf_size = 6,
+};
+
+/*
+ * =========================================================================
+ * System Fabric nodes
+ *
+ * 17 masters, 9 slaves, 2 tiered slaves
+ * Master ports: see enum msm_bus_8660_master_ports_type in webOS
+ * Slave ports: APPSS_FAB=0, SPS=1, SYSTEM_IMEM=2, SMPSS=3, MSS=4,
+ * LPASS=5, CPSS_FPB=6, SYSTEM_FPB=7, MMSS_FPB=8
+ * Default target: tiered slave 1 (APPSS gateway)
+ * =========================================================================
+ */
+DEFINE_QNODE(sfab_mas_appss, MSM8660_SFAB_MAS_APPSS, 8, 0, -1, ARB_TIER2,
+ MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(sfab_mas_sps, MSM8660_SFAB_MAS_SPS, 8, 1, -1, ARB_TIER2,
+ MSM8660_SFAB_SLV_SPS);
+/*
+ * ADM DMA masters - route through SFAB_TO_APPSS to reach EBI memory.
+ * Path: ADM -> SFAB_TO_APPSS -> AFAB_TO_SYSTEM -> AFAB_SLV_EBI_CH0
+ * This enables proper EBI bandwidth voting for DMA operations.
+ */
+DEFINE_QNODE(sfab_mas_adm0_port0, MSM8660_SFAB_MAS_ADM0_PORT0, 8, 2, -1, ARB_TIER2,
+ MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm0_port1, MSM8660_SFAB_MAS_ADM0_PORT1, 8, 3, -1, ARB_TIER2,
+ MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm1_port0, MSM8660_SFAB_MAS_ADM1_PORT0, 8, 4, -1, ARB_TIER2,
+ MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_adm1_port1, MSM8660_SFAB_MAS_ADM1_PORT1, 8, 5, -1, ARB_TIER2,
+ MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_mas_lpass_proc, MSM8660_SFAB_MAS_LPASS_PROC, 8, 6, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_proci, MSM8660_SFAB_MAS_MSS_PROCI, 8, 7, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_procd, MSM8660_SFAB_MAS_MSS_PROCD, 8, 8, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_mdm_port0, MSM8660_SFAB_MAS_MSS_MDM_PORT0, 8, 9, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_lpass, MSM8660_SFAB_MAS_LPASS, 8, 10, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mmss_fpb, MSM8660_SFAB_MAS_MMSS_FPB, 8, 13, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_adm1_ci, MSM8660_SFAB_MAS_ADM1_CI, 8, 14, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_adm0_ci, MSM8660_SFAB_MAS_ADM0_CI, 8, 15, -1, ARB_TIER2);
+DEFINE_QNODE(sfab_mas_mss_mdm_port1, MSM8660_SFAB_MAS_MSS_MDM_PORT1, 8, 16, -1, ARB_TIER2);
+/* USB HS has no dedicated master port in webOS SFAB - bandwidth voting only */
+DEFINE_QNODE(sfab_mas_usb_hs, MSM8660_SFAB_MAS_USB_HS, 8, -1, -1, 0,
+ MSM8660_SFAB_TO_APPSS);
+DEFINE_QNODE(sfab_to_appss, MSM8660_SFAB_TO_APPSS, 8, -1, 0, 0,
+ MSM8660_AFAB_TO_SYSTEM);
+DEFINE_QNODE(sfab_to_system_fpb, MSM8660_SFAB_TO_SYSTEM_FPB, 4, -1, 7, 0);
+DEFINE_QNODE(sfab_to_cpss_fpb, MSM8660_SFAB_TO_CPSS_FPB, 4, -1, 6, 0);
+DEFINE_QNODE(sfab_slv_sps, MSM8660_SFAB_SLV_SPS, 8, -1, 1, 0);
+DEFINE_QNODE(sfab_slv_system_imem, MSM8660_SFAB_SLV_SYSTEM_IMEM, 8, -1, 2, 0);
+DEFINE_QNODE(sfab_slv_ampss, MSM8660_SFAB_SLV_AMPSS, 8, -1, 3, 0);
+DEFINE_QNODE(sfab_slv_mss, MSM8660_SFAB_SLV_MSS, 8, -1, 4, 0);
+DEFINE_QNODE(sfab_slv_lpass, MSM8660_SFAB_SLV_LPASS, 8, -1, 5, 0);
+DEFINE_QNODE(sfab_slv_mmss_fpb, MSM8660_SFAB_SLV_MMSS_FPB, 8, -1, 8, 0);
+/*
+ * Gateway to DFAB: links to DFAB_TO_SFAB for path traversal.
+ * Also links to SFAB_TO_APPSS to enable DFAB->SFAB->AFAB->memory paths.
+ * No slave port in webOS SFAB config (DFAB is separate fabric).
+ */
+DEFINE_QNODE(sfab_to_dfab, MSM8660_SFAB_TO_DFAB, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB, MSM8660_SFAB_TO_APPSS);
+
+static struct msm8660_icc_node * const msm8660_sfab_nodes[] = {
+ [SFAB_MAS_APPSS] = &sfab_mas_appss,
+ [SFAB_MAS_SPS] = &sfab_mas_sps,
+ [SFAB_MAS_ADM0_PORT0] = &sfab_mas_adm0_port0,
+ [SFAB_MAS_ADM0_PORT1] = &sfab_mas_adm0_port1,
+ [SFAB_MAS_ADM1_PORT0] = &sfab_mas_adm1_port0,
+ [SFAB_MAS_ADM1_PORT1] = &sfab_mas_adm1_port1,
+ [SFAB_MAS_LPASS_PROC] = &sfab_mas_lpass_proc,
+ [SFAB_MAS_MSS_PROCI] = &sfab_mas_mss_proci,
+ [SFAB_MAS_MSS_PROCD] = &sfab_mas_mss_procd,
+ [SFAB_MAS_MSS_MDM_PORT0] = &sfab_mas_mss_mdm_port0,
+ [SFAB_MAS_LPASS] = &sfab_mas_lpass,
+ [SFAB_MAS_MMSS_FPB] = &sfab_mas_mmss_fpb,
+ [SFAB_MAS_ADM1_CI] = &sfab_mas_adm1_ci,
+ [SFAB_MAS_ADM0_CI] = &sfab_mas_adm0_ci,
+ [SFAB_MAS_MSS_MDM_PORT1] = &sfab_mas_mss_mdm_port1,
+ [SFAB_MAS_USB_HS] = &sfab_mas_usb_hs,
+ [SFAB_TO_APPSS] = &sfab_to_appss,
+ [SFAB_TO_SYSTEM_FPB] = &sfab_to_system_fpb,
+ [SFAB_TO_CPSS_FPB] = &sfab_to_cpss_fpb,
+ [SFAB_SLV_SPS] = &sfab_slv_sps,
+ [SFAB_SLV_SYSTEM_IMEM] = &sfab_slv_system_imem,
+ [SFAB_SLV_AMPSS] = &sfab_slv_ampss,
+ [SFAB_SLV_MSS] = &sfab_slv_mss,
+ [SFAB_SLV_LPASS] = &sfab_slv_lpass,
+ [SFAB_SLV_MMSS_FPB] = &sfab_slv_mmss_fpb,
+ [SFAB_TO_DFAB] = &sfab_to_dfab,
+};
+
+static const struct msm8660_icc_desc msm8660_sfab = {
+ .nodes = msm8660_sfab_nodes,
+ .num_nodes = ARRAY_SIZE(msm8660_sfab_nodes),
+ .bus_clks = msm8660_sfab_clocks,
+ .num_clks = ARRAY_SIZE(msm8660_sfab_clocks),
+ .rpm_resource = QCOM_RPM_SYS_FABRIC_ARB,
+ .nmasters = 17,
+ .nslaves = 9,
+ .ntieredslaves = 2,
+ .default_tiered_slave = 1, /* APPSS gateway */
+ .rpm_buf_size = 22,
+};
+
+/*
+ * =========================================================================
+ * MMSS Fabric nodes - Multimedia subsystem (MDP, camera, video, GPU)
+ *
+ * 14 masters, 4 slaves, 3 tiered slaves
+ * Master ports: MDP0=0, MDP1=1, ADM1=2, ROT=3, 3D=4, JPEG_DEC=5,
+ * 2D_CORE0=6, VFE=7, VPE=8, JPEG_ENC=9, 2D_CORE1=10,
+ * (APPS_FAB=11), HD_CODEC0=12, HD_CODEC1=13
+ * Slave ports: SMI=0, APPSS_FAB=1, (APPSS_FAB_1=2), MM_IMEM=3
+ * Tiered slaves: SMI=1, APPSS_FAB=2, MM_IMEM=3
+ * Default target: tiered slave 2 (APPSS gateway -> main memory)
+ *
+ * MDP ports get TIER1 (high priority) for guaranteed display refresh.
+ * All other masters get TIER2 (default priority).
+ * =========================================================================
+ */
+DEFINE_QNODE(mmfab_mas_mdp_port0, MSM8660_MMFAB_MAS_MDP_PORT0, 16, 0, -1, ARB_TIER1,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_mdp_port1, MSM8660_MMFAB_MAS_MDP_PORT1, 16, 1, -1, ARB_TIER1,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_adm1_port0, MSM8660_MMFAB_MAS_ADM1_PORT0, 8, 2, -1, ARB_TIER2);
+DEFINE_QNODE(mmfab_mas_rotator, MSM8660_MMFAB_MAS_ROTATOR, 16, 3, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_3d, MSM8660_MMFAB_MAS_GRAPHICS_3D, 16, 4, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_jpeg_dec, MSM8660_MMFAB_MAS_JPEG_DEC, 16, 5, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_2d_core0, MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE0, 16,
+ 6, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_vfe, MSM8660_MMFAB_MAS_VFE, 16, 7, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_vpe, MSM8660_MMFAB_MAS_VPE, 16, 8, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_jpeg_enc, MSM8660_MMFAB_MAS_JPEG_ENC, 16, 9, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_graphics_2d_core1, MSM8660_MMFAB_MAS_GRAPHICS_2D_CORE1, 16,
+ 10, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_hd_codec_port0, MSM8660_MMFAB_MAS_HD_CODEC_PORT0, 16,
+ 12, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+DEFINE_QNODE(mmfab_mas_hd_codec_port1, MSM8660_MMFAB_MAS_HD_CODEC_PORT1, 16,
+ 13, -1, ARB_TIER2,
+ MSM8660_MMFAB_SLV_SMI, MSM8660_MMFAB_TO_APPSS);
+/*
+ * Gateway from APPSS into MMSS: slave (port 1) for outbound traffic
+ * leaving MMSS, AND master (port 2) for inbound traffic arriving from
+ * APPSS (e.g. CPU memremap_wc accesses to SMI BOs). Without the master
+ * port and the forward links into MMFAB slaves (SMI / MM_IMEM), the
+ * ICC path-finder has no route from AMPSS_M0 to MMFAB_SLV_SMI; the
+ * cross-fabric gateway only worked for outbound traffic (MMSS masters
+ * reaching APPSS slaves like EBI).
+ *
+ * ARB_TIER1 keeps AMPSS->SMI traffic high-priority within MMFAB so
+ * CPU mmap reads/writes to SMI BOs don't get starved by MDP scanout.
+ */
+DEFINE_QNODE(mmfab_to_appss, MSM8660_MMFAB_TO_APPSS, 8, 2, 1, ARB_TIER1,
+ MSM8660_AFAB_TO_MMSS,
+ MSM8660_MMFAB_SLV_SMI,
+ MSM8660_MMFAB_SLV_MM_IMEM);
+DEFINE_QNODE(mmfab_slv_smi, MSM8660_MMFAB_SLV_SMI, 16, -1, 0, 0);
+DEFINE_QNODE(mmfab_slv_mm_imem, MSM8660_MMFAB_SLV_MM_IMEM, 8, -1, 3, 0);
+
+static struct msm8660_icc_node * const msm8660_mmfab_nodes[] = {
+ [MMFAB_MAS_MDP_PORT0] = &mmfab_mas_mdp_port0,
+ [MMFAB_MAS_MDP_PORT1] = &mmfab_mas_mdp_port1,
+ [MMFAB_MAS_ADM1_PORT0] = &mmfab_mas_adm1_port0,
+ [MMFAB_MAS_ROTATOR] = &mmfab_mas_rotator,
+ [MMFAB_MAS_GRAPHICS_3D] = &mmfab_mas_graphics_3d,
+ [MMFAB_MAS_JPEG_DEC] = &mmfab_mas_jpeg_dec,
+ [MMFAB_MAS_GRAPHICS_2D_CORE0] = &mmfab_mas_graphics_2d_core0,
+ [MMFAB_MAS_VFE] = &mmfab_mas_vfe,
+ [MMFAB_MAS_VPE] = &mmfab_mas_vpe,
+ [MMFAB_MAS_JPEG_ENC] = &mmfab_mas_jpeg_enc,
+ [MMFAB_MAS_GRAPHICS_2D_CORE1] = &mmfab_mas_graphics_2d_core1,
+ [MMFAB_MAS_HD_CODEC_PORT0] = &mmfab_mas_hd_codec_port0,
+ [MMFAB_MAS_HD_CODEC_PORT1] = &mmfab_mas_hd_codec_port1,
+ [MMFAB_TO_APPSS] = &mmfab_to_appss,
+ [MMFAB_SLV_SMI] = &mmfab_slv_smi,
+ [MMFAB_SLV_MM_IMEM] = &mmfab_slv_mm_imem,
+};
+
+static const struct msm8660_icc_desc msm8660_mmfab = {
+ .nodes = msm8660_mmfab_nodes,
+ .num_nodes = ARRAY_SIZE(msm8660_mmfab_nodes),
+ .bus_clks = msm8660_mmfab_clocks,
+ .num_clks = ARRAY_SIZE(msm8660_mmfab_clocks),
+ .rpm_resource = QCOM_RPM_MM_FABRIC_ARB,
+ .nmasters = 14,
+ .nslaves = 4,
+ .ntieredslaves = 3,
+ .default_tiered_slave = 2, /* APPSS gateway */
+ .rpm_buf_size = 23,
+};
+
+/*
+ * =========================================================================
+ * Daytona Fabric (DFAB) nodes - peripheral bus for SDCC and ADM DMA
+ *
+ * DFAB connects slower peripherals to SFAB via the DFAB_TO_SFAB gateway.
+ * SDCC controllers (eMMC, SD card) connect here.
+ *
+ * No RPM ARB for DFAB - it's a simple peripheral bus with clock-only control.
+ *
+ * USB HS is included as a DFAB voter for compatibility with the legacy
+ * webOS kernel clock voting mechanism.
+ * =========================================================================
+ */
+DEFINE_QNODE(dfab_mas_sdc1, MSM8660_DFAB_MAS_SDC1, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc2, MSM8660_DFAB_MAS_SDC2, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc3, MSM8660_DFAB_MAS_SDC3, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc4, MSM8660_DFAB_MAS_SDC4, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_sdc5, MSM8660_DFAB_MAS_SDC5, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_adm0_master, MSM8660_DFAB_MAS_ADM0_MASTER, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_mas_adm1_master, MSM8660_DFAB_MAS_ADM1_MASTER, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+DEFINE_QNODE(dfab_to_sfab, MSM8660_DFAB_TO_SFAB, 8, -1, -1, 0,
+ MSM8660_SFAB_TO_DFAB);
+DEFINE_QNODE(dfab_slv_sdc1, MSM8660_DFAB_SLV_SDC1, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc2, MSM8660_DFAB_SLV_SDC2, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc3, MSM8660_DFAB_SLV_SDC3, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc4, MSM8660_DFAB_SLV_SDC4, 8, -1, -1, 0);
+DEFINE_QNODE(dfab_slv_sdc5, MSM8660_DFAB_SLV_SDC5, 8, -1, -1, 0);
+/* USB HS DFAB voter - keeps DFAB clock stable during USB activity */
+DEFINE_QNODE(dfab_mas_usb_hs, MSM8660_DFAB_MAS_USB_HS, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+/* DSPS DFAB voter - keeps DFAB clock stable during sensor activity */
+DEFINE_QNODE(dfab_mas_dsps, MSM8660_DFAB_MAS_DSPS, 8, -1, -1, 0,
+ MSM8660_DFAB_TO_SFAB);
+
+static struct msm8660_icc_node * const msm8660_dfab_nodes[] = {
+ [DFAB_MAS_SDC1] = &dfab_mas_sdc1,
+ [DFAB_MAS_SDC2] = &dfab_mas_sdc2,
+ [DFAB_MAS_SDC3] = &dfab_mas_sdc3,
+ [DFAB_MAS_SDC4] = &dfab_mas_sdc4,
+ [DFAB_MAS_SDC5] = &dfab_mas_sdc5,
+ [DFAB_MAS_ADM0_MASTER] = &dfab_mas_adm0_master,
+ [DFAB_MAS_ADM1_MASTER] = &dfab_mas_adm1_master,
+ [DFAB_TO_SFAB] = &dfab_to_sfab,
+ [DFAB_SLV_SDC1] = &dfab_slv_sdc1,
+ [DFAB_SLV_SDC2] = &dfab_slv_sdc2,
+ [DFAB_SLV_SDC3] = &dfab_slv_sdc3,
+ [DFAB_SLV_SDC4] = &dfab_slv_sdc4,
+ [DFAB_SLV_SDC5] = &dfab_slv_sdc5,
+ [DFAB_MAS_USB_HS] = &dfab_mas_usb_hs,
+ [DFAB_MAS_DSPS] = &dfab_mas_dsps,
+};
+
+static const struct msm8660_icc_desc msm8660_dfab = {
+ .nodes = msm8660_dfab_nodes,
+ .num_nodes = ARRAY_SIZE(msm8660_dfab_nodes),
+ .bus_clks = msm8660_dfab_clocks,
+ .num_clks = ARRAY_SIZE(msm8660_dfab_clocks),
+ .rpm_resource = -1, /* No RPM ARB for DFAB */
+};
+
+/*
+ * Pack bwsum[] and arb[] arrays into the u32 RPM buffer.
+ *
+ * Two u16 values are packed per u32 word: lower 16 bits first, upper 16 next.
+ * Layout: [bwsum pairs] then [arb pairs], handling odd boundaries.
+ *
+ * This matches the webOS msm_bus_fabric_rpm_commit() packing algorithm.
+ */
+static void msm8660_pack_rpm_data(const u16 *bwsum, int nslaves,
+ const u16 *arb, int arb_size,
+ u32 *buf)
+{
+ int i, index = 0;
+
+ /* Pack bwsum pairs */
+ for (i = 0; i + 1 < nslaves; i += 2) {
+ buf[index] = ((u32)bwsum[i + 1] << 16) | bwsum[i];
+ index++;
+ }
+
+ /* Handle boundary between bwsum and arb for odd nslaves */
+ if (nslaves & 1) {
+ buf[index] = ((u32)arb[0] << 16) | bwsum[i];
+ index++;
+ i = 1; /* Start arb from index 1 (index 0 already packed) */
+ } else {
+ i = 0;
+ }
+
+ /* Pack arb pairs */
+ for (; i + 1 < arb_size; i += 2) {
+ buf[index] = ((u32)arb[i + 1] << 16) | arb[i];
+ index++;
+ }
+
+ /* Handle odd arb entry at end */
+ if (i < arb_size) {
+ buf[index] = arb[i];
+ index++;
+ }
+}
+
+/*
+ * Send fabric arbitration data to RPM.
+ *
+ * Iterates over all ICC nodes in the provider, builds bwsum/arb arrays
+ * from their aggregated bandwidth, and sends the packed data to RPM.
+ */
+static void msm8660_rpm_commit(struct msm8660_icc_provider *qp)
+{
+ const struct msm8660_icc_desc *desc = qp->desc;
+ struct icc_provider *provider = &qp->provider;
+ int nm = desc->nmasters;
+ int ns = desc->nslaves;
+ int nts = desc->ntieredslaves;
+ int arb_size = nm * nts;
+ int def_ts = desc->default_tiered_slave;
+ struct icc_node *n;
+ int ret;
+
+ memset(qp->bwsum, 0, ns * sizeof(u16));
+ memset(qp->arb, 0, arb_size * sizeof(u16));
+ memset(qp->rpm_buf, 0, desc->rpm_buf_size * sizeof(u32));
+
+ list_for_each_entry(n, &provider->nodes, node_list) {
+ struct msm8660_icc_node *qn = n->data;
+ u64 bw_bytes;
+ u16 bw_128k;
+
+ /* Use max of avg and peak bandwidth, convert to 128KB units */
+ bw_bytes = max(icc_units_to_bps(n->avg_bw),
+ icc_units_to_bps(n->peak_bw));
+ do_div(bw_bytes, 8); /* bits -> bytes */
+ bw_128k = (u16)min_t(u64, bw_bytes >> 17, ARB_BWMASK);
+
+ /* Set arb entry for masters at their default tiered slave */
+ if (qn->mas_port >= 0 && qn->mas_port < nm && def_ts > 0) {
+ int idx = (def_ts - 1) * nm + qn->mas_port;
+ u8 tier;
+
+ if (idx < arb_size) {
+ tier = bw_128k ? qn->mas_tier : ARB_TIER2;
+ qp->arb[idx] = (tier == ARB_TIER1 ? ARB_TIERMASK : 0)
+ | (bw_128k & ARB_BWMASK);
+ }
+ }
+
+ /* Set bwsum for slaves */
+ if (qn->slv_port >= 0 && qn->slv_port < ns)
+ qp->bwsum[qn->slv_port] = bw_128k;
+ }
+
+ msm8660_pack_rpm_data(qp->bwsum, ns, qp->arb, arb_size, qp->rpm_buf);
+
+ ret = qcom_rpm_write(qp->rpm, QCOM_RPM_ACTIVE_STATE,
+ desc->rpm_resource, qp->rpm_buf,
+ desc->rpm_buf_size);
+ if (ret)
+ dev_err_ratelimited(provider->dev,
+ "RPM fabric ARB write failed: %d\n", ret);
+}
+
+static int msm8660_icc_set(struct icc_node *src, struct icc_node *dst)
+{
+ struct msm8660_icc_node *src_qn;
+ struct msm8660_icc_provider *qp;
+ u64 sum_bw, max_peak_bw, rate;
+ u32 agg_avg = 0, agg_peak = 0;
+ struct icc_provider *provider;
+ struct icc_node *n;
+ int ret, i;
+
+ src_qn = src->data;
+ provider = src->provider;
+ qp = to_msm8660_icc_provider(provider);
+
+ list_for_each_entry(n, &provider->nodes, node_list)
+ provider->aggregate(n, 0, n->avg_bw, n->peak_bw,
+ &agg_avg, &agg_peak);
+
+ sum_bw = icc_units_to_bps(agg_avg);
+ max_peak_bw = icc_units_to_bps(agg_peak);
+
+ rate = max(sum_bw, max_peak_bw);
+ do_div(rate, src_qn->buswidth);
+ /* Apply minimum floor to prevent bus starvation */
+ rate = max_t(u64, rate, MSM8660_FABRIC_MIN_RATE);
+ rate = min_t(u32, rate, INT_MAX);
+
+ if (src_qn->rate != rate) {
+ for (i = 0; i < qp->num_clks; i++) {
+ ret = clk_set_rate(qp->bus_clks[i].clk, rate);
+ if (ret) {
+ dev_err(provider->dev,
+ "%s clk_set_rate error: %d\n",
+ qp->bus_clks[i].id, ret);
+ ret = 0;
+ }
+ }
+ src_qn->rate = rate;
+ }
+
+ /* Send RPM fabric arbitration if available */
+ if (qp->rpm && qp->desc->rpm_resource >= 0)
+ msm8660_rpm_commit(qp);
+
+ return 0;
+}
+
+static int msm8660_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
+{
+ *avg = 0;
+ *peak = 0;
+ return 0;
+}
+
+static struct qcom_rpm *msm8660_get_rpm(struct device *dev)
+{
+ struct device_node *rpm_np;
+ struct platform_device *rpm_pdev;
+ struct qcom_rpm *rpm;
+
+ rpm_np = of_parse_phandle(dev->of_node, "qcom,rpm", 0);
+ if (!rpm_np) {
+ dev_dbg(dev, "no qcom,rpm phandle, RPM ARB disabled\n");
+ return NULL;
+ }
+
+ rpm_pdev = of_find_device_by_node(rpm_np);
+ of_node_put(rpm_np);
+ if (!rpm_pdev) {
+ dev_warn(dev, "RPM device not found, ARB disabled\n");
+ return NULL;
+ }
+
+ rpm = dev_get_drvdata(&rpm_pdev->dev);
+ put_device(&rpm_pdev->dev);
+ if (!rpm) {
+ dev_warn(dev, "RPM not ready, ARB disabled\n");
+ return NULL;
+ }
+
+ return rpm;
+}
+
+static int msm8660_icc_probe(struct platform_device *pdev)
+{
+ const struct msm8660_icc_desc *desc;
+ struct msm8660_icc_node * const *qnodes;
+ struct msm8660_icc_provider *qp;
+ struct device *dev = &pdev->dev;
+ struct icc_onecell_data *data;
+ struct icc_provider *provider;
+ struct icc_node *node;
+ size_t num_nodes, i;
+ int ret;
+
+ desc = of_device_get_match_data(dev);
+ if (!desc)
+ return -EINVAL;
+
+ qnodes = desc->nodes;
+ num_nodes = desc->num_nodes;
+
+ qp = devm_kzalloc(dev, sizeof(*qp), GFP_KERNEL);
+ if (!qp)
+ return -ENOMEM;
+
+ data = devm_kzalloc(dev, struct_size(data, nodes, num_nodes),
+ GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+ data->num_nodes = num_nodes;
+
+ qp->bus_clks = devm_kmemdup(dev, desc->bus_clks,
+ desc->num_clks * sizeof(*desc->bus_clks),
+ GFP_KERNEL);
+ if (!qp->bus_clks)
+ return -ENOMEM;
+
+ qp->num_clks = desc->num_clks;
+
+ /*
+ * MSM8660 fabric clocks are managed by RPM firmware and may not be
+ * available in mainline Linux yet. Make them optional.
+ */
+ ret = devm_clk_bulk_get_optional(dev, qp->num_clks, qp->bus_clks);
+ if (ret) {
+ dev_warn(dev, "Failed to get bus clocks: %d (continuing anyway)\n", ret);
+ qp->num_clks = 0;
+ }
+
+ if (qp->num_clks) {
+ ret = clk_bulk_prepare_enable(qp->num_clks, qp->bus_clks);
+ if (ret) {
+ dev_warn(dev, "Failed to enable bus clocks: %d\n", ret);
+ qp->num_clks = 0;
+ }
+ }
+
+ /* Set up RPM fabric arbitration */
+ qp->desc = desc;
+ if (desc->rpm_resource >= 0) {
+ qp->rpm = msm8660_get_rpm(dev);
+ if (qp->rpm) {
+ int arb_size = desc->nmasters * desc->ntieredslaves;
+
+ qp->bwsum = devm_kcalloc(dev, desc->nslaves,
+ sizeof(u16), GFP_KERNEL);
+ qp->arb = devm_kcalloc(dev, arb_size + 1,
+ sizeof(u16), GFP_KERNEL);
+ qp->rpm_buf = devm_kcalloc(dev, desc->rpm_buf_size,
+ sizeof(u32), GFP_KERNEL);
+ if (!qp->bwsum || !qp->arb || !qp->rpm_buf) {
+ dev_warn(dev, "RPM buffer alloc failed, ARB disabled\n");
+ qp->rpm = NULL;
+ } else {
+ int rc;
+
+ dev_info(dev, "RPM fabric ARB enabled (%d masters, %d slaves, %d tiered)\n",
+ desc->nmasters, desc->nslaves,
+ desc->ntieredslaves);
+
+ /*
+ * One-shot sleep-context vote of zero bandwidth.
+ * Without an explicit SLEEP_STATE write, RPM has no
+ * fabric bandwidth target for deep-sleep and may
+ * keep the active vote applied indefinitely,
+ * preventing DDR from dropping its rate when CPUs
+ * power-collapse. The buffer is devm_kcalloc'd so
+ * it is all-zero at this point — written before
+ * any consumer can drive an active vote that would
+ * dirty it.
+ *
+ * msm8660_rpm_commit() writes ACTIVE_STATE only;
+ * SLEEP_STATE remains zero for the provider's
+ * lifetime, so this vote does not need refreshing.
+ */
+ rc = qcom_rpm_write(qp->rpm,
+ QCOM_RPM_SLEEP_STATE,
+ desc->rpm_resource,
+ qp->rpm_buf,
+ desc->rpm_buf_size);
+ if (rc)
+ dev_warn(dev, "RPM fabric sleep vote failed: %d\n",
+ rc);
+ }
+ }
+ }
+
+ provider = &qp->provider;
+ provider->dev = dev;
+ provider->set = msm8660_icc_set;
+ provider->aggregate = icc_std_aggregate;
+ provider->xlate = of_icc_xlate_onecell;
+ provider->data = data;
+ provider->get_bw = msm8660_get_bw;
+
+ icc_provider_init(provider);
+
+ for (i = 0; i < num_nodes; i++) {
+ size_t j;
+
+ if (!qnodes[i])
+ continue;
+
+ node = icc_node_create(qnodes[i]->id);
+ if (IS_ERR(node)) {
+ ret = PTR_ERR(node);
+ goto err_remove_nodes;
+ }
+
+ node->name = qnodes[i]->name;
+ node->data = qnodes[i];
+ icc_node_add(node, provider);
+
+ dev_dbg(dev, "registered node %s\n", node->name);
+
+ /* populate links */
+ for (j = 0; j < qnodes[i]->num_links; j++)
+ icc_link_create(node, qnodes[i]->links[j]);
+
+ data->nodes[i] = node;
+ }
+
+ ret = icc_provider_register(provider);
+ if (ret)
+ goto err_remove_nodes;
+
+ platform_set_drvdata(pdev, qp);
+
+ dev_info(dev, "MSM8660 interconnect provider registered\n");
+
+ return 0;
+
+err_remove_nodes:
+ icc_nodes_remove(provider);
+ clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+
+ return ret;
+}
+
+static void msm8660_icc_remove(struct platform_device *pdev)
+{
+ struct msm8660_icc_provider *qp = platform_get_drvdata(pdev);
+
+ icc_provider_deregister(&qp->provider);
+ icc_nodes_remove(&qp->provider);
+ if (qp->num_clks)
+ clk_bulk_disable_unprepare(qp->num_clks, qp->bus_clks);
+}
+
+static const struct of_device_id msm8660_noc_of_match[] = {
+ { .compatible = "qcom,msm8660-apps-fabric", .data = &msm8660_afab },
+ { .compatible = "qcom,msm8660-system-fabric", .data = &msm8660_sfab },
+ { .compatible = "qcom,msm8660-mmss-fabric", .data = &msm8660_mmfab },
+ { .compatible = "qcom,msm8660-daytona-fabric", .data = &msm8660_dfab },
+ { },
+};
+MODULE_DEVICE_TABLE(of, msm8660_noc_of_match);
+
+static struct platform_driver msm8660_noc_driver = {
+ .probe = msm8660_icc_probe,
+ .remove = msm8660_icc_remove,
+ .driver = {
+ .name = "qnoc-msm8660",
+ .of_match_table = msm8660_noc_of_match,
+ .sync_state = icc_sync_state,
+ },
+};
+/*
+ * Register the NOC provider at core_initcall, matching the mainline pattern
+ * used by newer Qualcomm SoCs (sm8450, glymur, qdu1000, sc8280xp, sm8750).
+ *
+ * Why not module_platform_driver (device_initcall)? drivers/Makefile lists
+ * drivers/interconnect/ at position 189, *after* every ICC-consumer subdir
+ * (clk/ @40, soc/ @46, gpu/ @68, base/mfd/ @76, spmi/ @89, usb/ @106,
+ * i2c/ @116, mmc/ @133, remoteproc/ @158). Within a single initcall level
+ * execution order = link order, so a device_initcall registration here runs
+ * *after* every consumer has already tried to probe. Mainline relies on
+ * deferred-probe retry to recover from that, but in this tree some consumer
+ * (apcs-msm8660 + cpufreq cascade suspected) fails to recover within
+ * deferred_probe_timeout=5 and boot dies at the Tux splash with no rootfs.
+ * Empirically confirmed 2026-05-29 with module_platform_driver (commits
+ * 99275d8a8ae9 + ca35c591854c, reverted).
+ *
+ * icc_provider_register does not require icc_init to have run first --
+ * the framework's locks are statically DEFINE_MUTEX'd -- so registering
+ * the provider at core_initcall (before icc_init at subsys_initcall) is
+ * safe, same as mainline sm8450 etc.
+ */
+static int __init msm8660_noc_driver_init(void)
+{
+ return platform_driver_register(&msm8660_noc_driver);
+}
+core_initcall(msm8660_noc_driver_init);
+
+static void __exit msm8660_noc_driver_exit(void)
+{
+ platform_driver_unregister(&msm8660_noc_driver);
+}
+module_exit(msm8660_noc_driver_exit);
+
+MODULE_DESCRIPTION("Qualcomm MSM8x60 interconnect driver");
+MODULE_LICENSE("GPL v2");
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) NoC driver
@ 2026-06-03 16:34 Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Herman van Hazendonk @ 2026-06-03 16:34 UTC (permalink / raw)
To: djakov
Cc: robh, krzk+dt, conor+dt, linux-pm, linux-arm-msm, devicetree,
linux-kernel, Herman van Hazendonk
Add interconnect support for the MSM8x60 family (MSM8260, MSM8660,
APQ8060) — Qualcomm's Scorpion dual-core SoC generation used in devices
like the HP TouchPad, HTC EVO 3D and HTC Sensation.
The chip's Network-on-Chip fabric is divided into four sub-fabrics:
AFAB - Applications fabric: Scorpion CPU + L2 cache, paths to EBI
SFAB - System fabric: DMA engines, SPS, USB HS, LPASS, MSS
MMFAB - Multimedia fabric: MDP, GPU, camera (VFE/JPEG), video codec
DFAB - Daytona fabric: eMMC/SD (SDC), ADM DMA, USB voter
Bandwidth requests are committed to the RPM firmware via its shared-memory
arbitration tables using the qcom-rpm driver backend; each fabric also has
a bus clock whose rate is derived from the aggregated bandwidth.
The driver includes a 384 MHz bus clock floor, replacing the previous
266 MHz minimum that caused USB starvation on real hardware, and careful
devm/device_link lifecycle to handle EPROBE_DEFER from both RPM and
clock lookups without leaking clock prepare/enable references.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
Herman van Hazendonk (2):
dt-bindings: interconnect: qcom: add msm8660 fabric IDs
interconnect: qcom: add MSM8x60 NoC driver
drivers/interconnect/qcom/Kconfig | 14 +
drivers/interconnect/qcom/Makefile | 2 +
drivers/interconnect/qcom/msm8660.c | 1147 +++++++++++++++++
.../dt-bindings/interconnect/qcom,msm8660.h | 156 +++
4 files changed, 1319 insertions(+)
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] 6+ messages in thread
* [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs
2026-06-03 16:34 [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) NoC driver Herman van Hazendonk
@ 2026-06-03 16:34 ` Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-06-03 22:50 ` [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) " Jie Gan
2 siblings, 0 replies; 6+ messages in thread
From: Herman van Hazendonk @ 2026-06-03 16:34 UTC (permalink / raw)
To: djakov
Cc: robh, krzk+dt, conor+dt, linux-pm, linux-arm-msm, devicetree,
linux-kernel, Herman van Hazendonk
Add the dt-binding interconnect master/slave ID header for the
MSM8x60 family (MSM8260/MSM8660/APQ8060) fabric mesh. The chip's
NoC fabric is split into multiple sub-fabrics that the qnoc-msm8660
driver models:
AFAB - Applications fabric (Scorpion CPU + L2)
SFAB - System fabric (DMA, SPS, security)
MMFAB - Multimedia fabric (MDP, GPU, camera, video, rotator)
DFAB - Daytona fabric (SDC, ADM master/slave)
SFPB - System Fast Peripheral Bridge (RPM, MPM, PMIC SSBI)
CFPB - CPU Subsystem Fast Peripheral Bus (GSBI UART/QUP, USB FS,
TSIF, TSSC, PDM, PRNG)
IDs derived from the legacy vendor msm_bus_board_8660.c master/slave
enums, normalised to the upstream interconnect-framework naming.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../dt-bindings/interconnect/qcom,msm8660.h | 156 ++++++++++++++++++
1 file changed, 156 insertions(+)
create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h
diff --git a/include/dt-bindings/interconnect/qcom,msm8660.h b/include/dt-bindings/interconnect/qcom,msm8660.h
new file mode 100644
index 000000000000..c9ce3f5a5276
--- /dev/null
+++ b/include/dt-bindings/interconnect/qcom,msm8660.h
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+/*
+ * Qualcomm MSM8x60 family (MSM8260/MSM8660/APQ8060) interconnect IDs
+ *
+ * Copyright (c) 2026 Herman van Hazendonk <github.com@herrie.org>
+ *
+ * Based on webOS kernel msm_bus_board_8660.c
+ * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ */
+
+#ifndef __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H
+#define __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H
+
+/*
+ * MSM8x60 has a fabric-based bus architecture:
+ * - APPSS Fabric: CPU and memory interface
+ * - System Fabric: System peripherals and DMA
+ * - MMSS Fabric: Multimedia subsystem (display, camera, video)
+ * - Daytona Fabric: Peripheral bus (SDCC, ADM DMA)
+ * - System FPB: System Fast Peripheral Bus
+ * - CPSS FPB: CPU Subsystem Fast Peripheral Bus
+ */
+
+/* APPSS Fabric - Apps processor fabric */
+#define AFAB_MAS_AMPSS_M0 0
+#define AFAB_MAS_AMPSS_M1 1
+#define AFAB_SLV_EBI_CH0 2
+#define AFAB_SLV_AMPSS_L2 3
+#define AFAB_TO_MMSS 4
+#define AFAB_TO_SYSTEM 5
+
+/* System Fabric - System bus */
+#define SFAB_MAS_APPSS 0
+#define SFAB_MAS_SPS 1
+#define SFAB_MAS_ADM0_PORT0 2
+#define SFAB_MAS_ADM0_PORT1 3
+#define SFAB_MAS_ADM1_PORT0 4
+#define SFAB_MAS_ADM1_PORT1 5
+#define SFAB_MAS_LPASS_PROC 6
+#define SFAB_MAS_MSS_PROCI 7
+#define SFAB_MAS_MSS_PROCD 8
+#define SFAB_MAS_MSS_MDM_PORT0 9
+#define SFAB_MAS_LPASS 10
+#define SFAB_MAS_MMSS_FPB 11
+#define SFAB_MAS_ADM1_CI 12
+#define SFAB_MAS_ADM0_CI 13
+#define SFAB_MAS_MSS_MDM_PORT1 14
+#define SFAB_MAS_USB_HS 15
+#define SFAB_TO_APPSS 16
+#define SFAB_TO_SYSTEM_FPB 17
+#define SFAB_TO_CPSS_FPB 18
+#define SFAB_SLV_SPS 19
+#define SFAB_SLV_SYSTEM_IMEM 20
+#define SFAB_SLV_AMPSS 21
+#define SFAB_SLV_MSS 22
+#define SFAB_SLV_LPASS 23
+#define SFAB_SLV_MMSS_FPB 24
+#define SFAB_TO_DFAB 25
+
+/* MMSS Fabric - Multimedia subsystem */
+#define MMFAB_MAS_MDP_PORT0 0
+#define MMFAB_MAS_MDP_PORT1 1
+#define MMFAB_MAS_ADM1_PORT0 2
+#define MMFAB_MAS_ROTATOR 3
+#define MMFAB_MAS_GRAPHICS_3D 4
+#define MMFAB_MAS_JPEG_DEC 5
+#define MMFAB_MAS_GRAPHICS_2D_CORE0 6
+#define MMFAB_MAS_VFE 7
+#define MMFAB_MAS_VPE 8
+#define MMFAB_MAS_JPEG_ENC 9
+#define MMFAB_MAS_GRAPHICS_2D_CORE1 10
+#define MMFAB_MAS_HD_CODEC_PORT0 11
+#define MMFAB_MAS_HD_CODEC_PORT1 12
+#define MMFAB_TO_APPSS 13
+#define MMFAB_SLV_SMI 14
+#define MMFAB_SLV_MM_IMEM 15
+
+/*
+ * Daytona Fabric (DFAB) - Peripheral bus
+ *
+ * DFAB connects slower peripherals (SDCC, ADM DMA) to the system fabric.
+ * The webOS kernel managed DFAB bandwidth via voter clocks (dfab_sdc*_clk,
+ * dfab_usb_hs_clk). In mainline, this is handled by the interconnect framework.
+ *
+ * USB HS is included as a DFAB voter for compatibility with the legacy clock
+ * voting mechanism. The webOS kernel comment said: "if usb link is in sps
+ * there is no need for usb pclk as daytona fabric clock will be used instead".
+ * This keeps DFAB clock stable when USB is active.
+ */
+#define DFAB_MAS_SDC1 0
+#define DFAB_MAS_SDC2 1
+#define DFAB_MAS_SDC3 2
+#define DFAB_MAS_SDC4 3
+#define DFAB_MAS_SDC5 4
+#define DFAB_MAS_ADM0_MASTER 5
+#define DFAB_MAS_ADM1_MASTER 6
+#define DFAB_TO_SFAB 7
+#define DFAB_SLV_SDC1 8
+#define DFAB_SLV_SDC2 9
+#define DFAB_SLV_SDC3 10
+#define DFAB_SLV_SDC4 11
+#define DFAB_SLV_SDC5 12
+#define DFAB_MAS_USB_HS 13
+#define DFAB_MAS_DSPS 14
+
+/* System FPB - Slow peripheral bus for system */
+#define SFPB_MAS_SYSTEM 0
+#define SFPB_MAS_SPDM 1
+#define SFPB_MAS_RPM 2
+#define SFPB_SLV_SPDM 3
+#define SFPB_SLV_RPM 4
+#define SFPB_SLV_RPM_MSG_RAM 5
+#define SFPB_SLV_MPM 6
+#define SFPB_SLV_PMIC1_SSBI1_A 7
+#define SFPB_SLV_PMIC1_SSBI1_B 8
+#define SFPB_SLV_PMIC1_SSBI1_C 9
+#define SFPB_SLV_PMIC2_SSBI2_A 10
+#define SFPB_SLV_PMIC2_SSBI2_B 11
+
+/* CPSS FPB - CPU subsystem fast peripheral bus */
+#define CFPB_MAS_SYSTEM 0
+#define CFPB_SLV_GSBI1_UART 1
+#define CFPB_SLV_GSBI2_UART 2
+#define CFPB_SLV_GSBI3_UART 3
+#define CFPB_SLV_GSBI4_UART 4
+#define CFPB_SLV_GSBI5_UART 5
+#define CFPB_SLV_GSBI6_UART 6
+#define CFPB_SLV_GSBI7_UART 7
+#define CFPB_SLV_GSBI8_UART 8
+#define CFPB_SLV_GSBI9_UART 9
+#define CFPB_SLV_GSBI10_UART 10
+#define CFPB_SLV_GSBI11_UART 11
+#define CFPB_SLV_GSBI12_UART 12
+#define CFPB_SLV_GSBI1_QUP 13
+#define CFPB_SLV_GSBI2_QUP 14
+#define CFPB_SLV_GSBI3_QUP 15
+#define CFPB_SLV_GSBI4_QUP 16
+#define CFPB_SLV_GSBI5_QUP 17
+#define CFPB_SLV_GSBI6_QUP 18
+#define CFPB_SLV_GSBI7_QUP 19
+#define CFPB_SLV_GSBI8_QUP 20
+#define CFPB_SLV_GSBI9_QUP 21
+#define CFPB_SLV_GSBI10_QUP 22
+#define CFPB_SLV_GSBI11_QUP 23
+#define CFPB_SLV_GSBI12_QUP 24
+#define CFPB_SLV_EBI2_NAND 25
+#define CFPB_SLV_USB_FS1 26
+#define CFPB_SLV_USB_FS2 27
+#define CFPB_SLV_TSIF 28
+#define CFPB_SLV_MSM_TSSC 29
+#define CFPB_SLV_MSM_PDM 30
+#define CFPB_SLV_MSM_DIMEM 31
+#define CFPB_SLV_MSM_TCSR 32
+#define CFPB_SLV_MSM_PRNG 33
+
+#endif /* __DT_BINDINGS_INTERCONNECT_QCOM_MSM8660_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-06-03 16:34 [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) NoC driver Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
@ 2026-06-03 16:34 ` Herman van Hazendonk
2026-06-03 22:54 ` Dmitry Baryshkov
2026-06-03 22:50 ` [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) " Jie Gan
2 siblings, 1 reply; 6+ messages in thread
From: Herman van Hazendonk @ 2026-06-03 16:34 UTC (permalink / raw)
To: djakov
Cc: robh, krzk+dt, conor+dt, linux-pm, linux-arm-msm, devicetree,
linux-kernel, Herman van Hazendonk
Add a Qualcomm interconnect driver for the MSM8x60 family modelling the
four NoC fabrics (APPSS, System, MMSS, DFAB) 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
aggregated bandwidth divided by the fabric bus width, with a
minimum floor of 384 MHz to prevent USB starvation observed when
the fabric drops to the previously-used 266 MHz minimum.
- 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.
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. 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 | 1147 +++++++++++++++++
.../dt-bindings/interconnect/qcom,msm8660.h | 6 +-
4 files changed, 1166 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..d0a8df7cc700 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
+ 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..f4bb852eaa2b
--- /dev/null
+++ b/drivers/interconnect/qcom/msm8660.c
@@ -0,0 +1,1147 @@
+// 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/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. 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
+ * @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 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)
+ * =========================================================================
+ */
+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 legacy vendor kernel 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 legacy vendor kernel 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
+ * legacy vendor 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 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_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);
+ /*
+ * 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) {
+ dev_dbg(dev, "RPM device not found yet, deferring probe\n");
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ /*
+ * 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) {
+ dev_warn(dev, "failed to add device link to RPM, deferring\n");
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+
+ /*
+ * 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) {
+ dev_dbg(dev, "RPM not ready, deferring probe\n");
+ device_link_remove(dev, &rpm_pdev->dev);
+ 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;
+ } 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;
+
+ 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);
+ /*
+ * 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] 6+ messages in thread
* Re: [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) NoC driver
2026-06-03 16:34 [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) NoC driver Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-06-03 22:50 ` Jie Gan
2 siblings, 0 replies; 6+ messages in thread
From: Jie Gan @ 2026-06-03 22:50 UTC (permalink / raw)
To: Herman van Hazendonk, djakov
Cc: robh, krzk+dt, conor+dt, linux-pm, linux-arm-msm, devicetree,
linux-kernel
On 6/4/2026 12:34 AM, Herman van Hazendonk wrote:
> Add interconnect support for the MSM8x60 family (MSM8260, MSM8660,
> APQ8060) — Qualcomm's Scorpion dual-core SoC generation used in devices
> like the HP TouchPad, HTC EVO 3D and HTC Sensation.
>
Missed dt-binding doc for the new added driver.
Thanks,
Jie
> The chip's Network-on-Chip fabric is divided into four sub-fabrics:
>
> AFAB - Applications fabric: Scorpion CPU + L2 cache, paths to EBI
> SFAB - System fabric: DMA engines, SPS, USB HS, LPASS, MSS
> MMFAB - Multimedia fabric: MDP, GPU, camera (VFE/JPEG), video codec
> DFAB - Daytona fabric: eMMC/SD (SDC), ADM DMA, USB voter
>
> Bandwidth requests are committed to the RPM firmware via its shared-memory
> arbitration tables using the qcom-rpm driver backend; each fabric also has
> a bus clock whose rate is derived from the aggregated bandwidth.
>
> The driver includes a 384 MHz bus clock floor, replacing the previous
> 266 MHz minimum that caused USB starvation on real hardware, and careful
> devm/device_link lifecycle to handle EPROBE_DEFER from both RPM and
> clock lookups without leaking clock prepare/enable references.
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
>
> Herman van Hazendonk (2):
> dt-bindings: interconnect: qcom: add msm8660 fabric IDs
> interconnect: qcom: add MSM8x60 NoC driver
>
> drivers/interconnect/qcom/Kconfig | 14 +
> drivers/interconnect/qcom/Makefile | 2 +
> drivers/interconnect/qcom/msm8660.c | 1147 +++++++++++++++++
> .../dt-bindings/interconnect/qcom,msm8660.h | 156 +++
> 4 files changed, 1319 insertions(+)
> create mode 100644 drivers/interconnect/qcom/msm8660.c
> create mode 100644 include/dt-bindings/interconnect/qcom,msm8660.h
>
>
> base-commit: 944125b4c454b58d2fe6e35f1087a932b2050dff
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver
2026-06-03 16:34 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
@ 2026-06-03 22:54 ` Dmitry Baryshkov
0 siblings, 0 replies; 6+ messages in thread
From: Dmitry Baryshkov @ 2026-06-03 22:54 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: djakov, robh, krzk+dt, conor+dt, linux-pm, linux-arm-msm,
devicetree, linux-kernel
On Wed, Jun 03, 2026 at 06:34:10PM +0200, Herman van Hazendonk wrote:
> Add a Qualcomm interconnect driver for the MSM8x60 family modelling the
> four NoC fabrics (APPSS, System, MMSS, DFAB) 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
> aggregated bandwidth divided by the fabric bus width, with a
> minimum floor of 384 MHz to prevent USB starvation observed when
> the fabric drops to the previously-used 266 MHz minimum.
> - 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.
>
> 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. 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.
Thanks for working on this.
>
> 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 | 1147 +++++++++++++++++
> .../dt-bindings/interconnect/qcom,msm8660.h | 6 +-
> 4 files changed, 1166 insertions(+), 3 deletions(-)
> create mode 100644 drivers/interconnect/qcom/msm8660.c
>
> +
> +/*
> + * 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" },
What clocks are those? Please provide the actual dt-bindings schema
(together with the examples). In the past we had bus / bus_a clocks for
the SMD-RPM-based interconnects, but lyter they were ripped out and
replaced with the direct voting for the resources.
> +};
> +
> +
> +/*
> + * 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, ...) \
Please expand the macro.
> + 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, \
> + }
> +
[...]
> +/*
> + * 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;
Here and further, return dev_err_ptr_probe().
> + }
> +
> + 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);
> + }
> +
> + /*
> + * 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) {
> + dev_warn(dev, "failed to add device link to RPM, deferring\n");
> + return ERR_PTR(-EPROBE_DEFER);
> + }
> +
> + /*
> + * 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) {
> + dev_dbg(dev, "RPM not ready, deferring probe\n");
> + device_link_remove(dev, &rpm_pdev->dev);
> + 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".
Please limit to -ENOENT only.
> + */
> + 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;
> + } 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;
> +
> + node = icc_node_create(qnodes[i]->id);
Please switch to the dynamic ICC node numbers (see icc-rpmh)
> + 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);
> +
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-06-03 22:54 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-03 16:34 [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) NoC driver Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 1/2] dt-bindings: interconnect: qcom: add msm8660 fabric IDs Herman van Hazendonk
2026-06-03 16:34 ` [PATCH 2/2] interconnect: qcom: add MSM8x60 NoC driver Herman van Hazendonk
2026-06-03 22:54 ` Dmitry Baryshkov
2026-06-03 22:50 ` [PATCH 0/2] interconnect: qcom: add MSM8x60 (MSM8660/APQ8060) " Jie Gan
-- strict thread matches above, loose matches on Subject: below --
2026-05-30 14:00 [PATCH 0/2] interconnect: qcom: add MSM8x60 " Herman van Hazendonk
2026-05-30 14:00 ` [PATCH 2/2] " 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