summaryrefslogtreecommitdiffstats
path: root/target/linux/qualcommax
diff options
context:
space:
mode:
authorRobert Marko <robimarko@gmail.com>2024-03-22 10:01:17 +0100
committerRobert Marko <robimarko@gmail.com>2024-03-22 21:19:21 +0100
commite8e7b3c106cac71c39c9672dfca615370b632dcd (patch)
tree28136441e2de90f6f9273046833fc3ce4f68c556 /target/linux/qualcommax
parente28492c81ac57c200798d62c60af4a288b076cfb (diff)
downloadopenwrt-e8e7b3c106cac71c39c9672dfca615370b632dcd.tar.gz
openwrt-e8e7b3c106cac71c39c9672dfca615370b632dcd.tar.bz2
openwrt-e8e7b3c106cac71c39c9672dfca615370b632dcd.zip
kernel/qualcommax: Restore kernel files for v6.1
This is an automatically generated commit which aids following Kernel patch history, as git will see the move and copy as a rename thus defeating the purpose. See: https://lists.openwrt.org/pipermail/openwrt-devel/2023-October/041673.html for the original discussion. Signed-off-by: Robert Marko <robimarko@gmail.com>
Diffstat (limited to 'target/linux/qualcommax')
-rw-r--r--target/linux/qualcommax/config-6.1540
-rw-r--r--target/linux/qualcommax/patches-6.1/0001-v6.2-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch32
-rw-r--r--target/linux/qualcommax/patches-6.1/0002-v6.2-thermal-drivers-tsens-Add-support-for-combined-inter.patch134
-rw-r--r--target/linux/qualcommax/patches-6.1/0003-v6.2-thermal-drivers-tsens-Allow-configuring-min-and-max-.patch101
-rw-r--r--target/linux/qualcommax/patches-6.1/0004-v6.2-thermal-drivers-tsens-Add-IPQ8074-support.patch74
-rw-r--r--target/linux/qualcommax/patches-6.1/0005-v6.2-arm64-dts-qcom-ipq8074-add-thermal-nodes.patch130
-rw-r--r--target/linux/qualcommax/patches-6.1/0006-v6.2-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch29
-rw-r--r--target/linux/qualcommax/patches-6.1/0007-v6.2-clk-qcom-ipq8074-convert-to-parent-data.patch3601
-rw-r--r--target/linux/qualcommax/patches-6.1/0008-v6.2-arm64-dts-qcom-ipq6018-fix-NAND-node-name.patch28
-rw-r--r--target/linux/qualcommax/patches-6.1/0009-v6.2-dt-bindings-clock-qcom-ipq8074-add-missing-networkin.patch39
-rw-r--r--target/linux/qualcommax/patches-6.1/0010-v6.2-clk-qcom-ipq8074-add-missing-networking-resets.patch41
-rw-r--r--target/linux/qualcommax/patches-6.1/0011-v6.2-clk-qcom-ipq8074-populate-fw_name-for-all-parents.patch152
-rw-r--r--target/linux/qualcommax/patches-6.1/0012-v6.2-arm64-dts-qcom-ipq8074-pass-XO-and-sleep-clocks-to-G.patch36
-rw-r--r--target/linux/qualcommax/patches-6.1/0013-v6.2-arm64-dts-qcom-add-PMP8074-DTSI.patch149
-rw-r--r--target/linux/qualcommax/patches-6.1/0014-v6.2-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch37
-rw-r--r--target/linux/qualcommax/patches-6.1/0015-v6.2-arm64-dts-qcom-hk01-use-GPIO-flags-for-tlmm.patch42
-rw-r--r--target/linux/qualcommax/patches-6.1/0016-v6.2-arm64-dts-qcom-ipq8074-Fix-up-comments.patch82
-rw-r--r--target/linux/qualcommax/patches-6.1/0017-v6.2-arm64-dts-qcom-ipq8074-align-TLMM-pin-configuration-.patch60
-rw-r--r--target/linux/qualcommax/patches-6.1/0018-v6.2-arm64-dts-qcom-ipq6018-align-TLMM-pin-configuration-.patch56
-rw-r--r--target/linux/qualcommax/patches-6.1/0019-v6.3-arm64-dts-qcom-ipq8074-set-Gen2-PCIe-pcie-max-link-s.patch24
-rw-r--r--target/linux/qualcommax/patches-6.1/0020-v6.3-PCI-qcom-Add-support-for-IPQ8074-Gen3-port.patch26
-rw-r--r--target/linux/qualcommax/patches-6.1/0021-v6.3-clk-qcom-ipq8074-populate-fw_name-for-usb3phy-s.patch38
-rw-r--r--target/linux/qualcommax/patches-6.1/0022-v6.4-arm64-dts-qcom-ipq8074-add-compatible-fallback-to.patch26
-rw-r--r--target/linux/qualcommax/patches-6.1/0023-v6.5-arm64-dts-qcom-ipq8074-add-critical-thermal-trips.patch199
-rw-r--r--target/linux/qualcommax/patches-6.1/0024-v6.7-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ8174-family.patch29
-rw-r--r--target/linux/qualcommax/patches-6.1/0025-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ6018.patch123
-rw-r--r--target/linux/qualcommax/patches-6.1/0026-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ8074.patch113
-rw-r--r--target/linux/qualcommax/patches-6.1/0027-v6.7-clk-qcom-apss-ipq6018-add-the-GPLL0-clock-also-as-cl.patch43
-rw-r--r--target/linux/qualcommax/patches-6.1/0028-v6.7-arm64-dts-qcom-ipq8074-include-the-GPLL0-as-clock-pr.patch32
-rw-r--r--target/linux/qualcommax/patches-6.1/0029-v6.3-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ5332-and-its.patch27
-rw-r--r--target/linux/qualcommax/patches-6.1/0030-v6.4-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ9574-and-its.patch33
-rw-r--r--target/linux/qualcommax/patches-6.1/0031-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5312-and-.patch28
-rw-r--r--target/linux/qualcommax/patches-6.1/0032-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5300.patch25
-rw-r--r--target/linux/qualcommax/patches-6.1/0033-v6.2-arm64-dts-qcom-ipq6018-move-ARMv8-timer-out-of-SoC.patch52
-rw-r--r--target/linux/qualcommax/patches-6.1/0034-v6.3-arm64-dts-qcom-ipq6018-Sort-nodes-properly.patch605
-rw-r--r--target/linux/qualcommax/patches-6.1/0035-v6.3-arm64-dts-qcom-ipq6018-Add-remove-some-newlines.patch92
-rw-r--r--target/linux/qualcommax/patches-6.1/0036-v6.3-arm64-dts-qcom-ipq6018-Use-lowercase-hex.patch25
-rw-r--r--target/linux/qualcommax/patches-6.1/0037-v6.3-arm64-dts-qcom-ipq6018-align-RPM-G-Link-node-with.patch28
-rw-r--r--target/linux/qualcommax/patches-6.1/0038-v6.4-arm64-dts-qcom-ipq6018-cp01-c1-drop-SPI-cs-select.patch27
-rw-r--r--target/linux/qualcommax/patches-6.1/0039-v6.5-arm64-dts-qcom-add-few-more-reserved-memory-region.patch92
-rw-r--r--target/linux/qualcommax/patches-6.1/0040-v6.5-arm64-dts-qcom-enable-the-download-mode-support.patch49
-rw-r--r--target/linux/qualcommax/patches-6.1/0041-v6.5-arm64-dts-qcom-ipq6018-correct-qrng-unit-address.patch29
-rw-r--r--target/linux/qualcommax/patches-6.1/0042-v6.5-arm64-dts-qcom-ipq6018-add-unit-address-to-soc-node.patch28
-rw-r--r--target/linux/qualcommax/patches-6.1/0043-v6.5-arm64-dts-qcom-ipq6018-add-QFPROM-node.patch34
-rw-r--r--target/linux/qualcommax/patches-6.1/0044-v6.5-arm64-dts-qcom-ipq6018-drop-incorrect-SPI-bus.patch37
-rw-r--r--target/linux/qualcommax/patches-6.1/0045-v6.5-arm64-dts-qcom-ipq8074-drop-incorrect-SPI-bus.patch29
-rw-r--r--target/linux/qualcommax/patches-6.1/0046-v6.6-clk-qcom-gcc-ipq6018-Use-floor-ops-for-sdcc-clocks.patch27
-rw-r--r--target/linux/qualcommax/patches-6.1/0047-v6.6-clk-qcom-gcc-ipq6018-drop-redundant-F-define.patch27
-rw-r--r--target/linux/qualcommax/patches-6.1/0048-v6.6-clk-qcom-gcc-ipq6018-update-UBI32-PLL.patch39
-rw-r--r--target/linux/qualcommax/patches-6.1/0049-v6.6-clk-qcom-gcc-ipq6018-remove-duplicate-initializers.patch38
-rw-r--r--target/linux/qualcommax/patches-6.1/0050-v6.6-soc-qcom-Add-RPM-processor-subsystem-driver.patch132
-rw-r--r--target/linux/qualcommax/patches-6.1/0051-v6.6-arm64-dts-qcom-Add-rpm-proc-node-for-GLINK.patch93
-rw-r--r--target/linux/qualcommax/patches-6.1/0052-v6.7-arm64-dts-qcom-ipq6018-include-the-GPLL0-as.patch35
-rw-r--r--target/linux/qualcommax/patches-6.1/0053-v6.7-clk-qcom-gcc-ipq6018-add-QUP6-I2C-clock.patch57
-rw-r--r--target/linux/qualcommax/patches-6.1/0054-v6.8-arm64-dts-qcom-ipq6018-use-CPUFreq-NVMEM.patch85
-rw-r--r--target/linux/qualcommax/patches-6.1/0055-v6.8-arm64-dts-ipq6018-Add-remaining-QUP-UART-node.patch81
-rw-r--r--target/linux/qualcommax/patches-6.1/0056-v6.9-arm64-dts-qcom-Fix-hs_phy_irq-for-QUSB2-targets.patch95
-rw-r--r--target/linux/qualcommax/patches-6.1/0057-v6.8-hwspinlock-qcom-Remove-IPQ6018-SOC-specific-.patch32
-rw-r--r--target/linux/qualcommax/patches-6.1/0058-v6.9-arm64-dts-qcom-ipq6018-add-tsens-node.patch34
-rw-r--r--target/linux/qualcommax/patches-6.1/0059-v6.9-arm64-dts-qcom-ipq6018-add-thermal-zones.patch180
-rw-r--r--target/linux/qualcommax/patches-6.1/0060-v6.9-clk-qcom-gcc-ipq6018-add-qdss_at-clock-needed-for-wi.patch50
-rw-r--r--target/linux/qualcommax/patches-6.1/0061-v6.8-phy-qcom-qmp-usb-fix-serdes-init-sequence-for-IPQ6018.patch102
-rw-r--r--target/linux/qualcommax/patches-6.1/0062-v6.8-arm64-dts-qcom-ipq8074-Add-QUP4-SPI-node.patch38
-rw-r--r--target/linux/qualcommax/patches-6.1/0100-clk-qcom-clk-rcg2-introduce-support-for-multiple-con.patch203
-rw-r--r--target/linux/qualcommax/patches-6.1/0101-clk-qcom-gcc-ipq8074-rework-nss_port5-6-clock-to-mul.patch129
-rw-r--r--target/linux/qualcommax/patches-6.1/0102-arm64-dts-ipq8074-add-reserved-memory-nodes.patch60
-rw-r--r--target/linux/qualcommax/patches-6.1/0110-arm64-dts-qcom-ipq8074-pass-QMP-PCI-PHY-PIPE-clocks-.patch30
-rw-r--r--target/linux/qualcommax/patches-6.1/0111-arm64-dts-qcom-ipq8074-use-msi-parent-for-PCIe.patch43
-rw-r--r--target/linux/qualcommax/patches-6.1/0112-remoteproc-qcom-Add-PRNG-proxy-clock.patch155
-rw-r--r--target/linux/qualcommax/patches-6.1/0113-remoteproc-qcom-Add-secure-PIL-support.patch143
-rw-r--r--target/linux/qualcommax/patches-6.1/0114-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch103
-rw-r--r--target/linux/qualcommax/patches-6.1/0115-remoteproc-qcom-Add-ssr-subdevice-identifier.patch24
-rw-r--r--target/linux/qualcommax/patches-6.1/0116-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch79
-rw-r--r--target/linux/qualcommax/patches-6.1/0117-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch26
-rw-r--r--target/linux/qualcommax/patches-6.1/0118-clk-qcom-Add-WCSSAON-reset.patch25
-rw-r--r--target/linux/qualcommax/patches-6.1/0119-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch48
-rw-r--r--target/linux/qualcommax/patches-6.1/0120-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch120
-rw-r--r--target/linux/qualcommax/patches-6.1/0121-arm64-dts-ipq8074-Add-WLAN-node.patch135
-rw-r--r--target/linux/qualcommax/patches-6.1/0122-arm64-dts-ipq8074-add-CPU-clock.patch59
-rw-r--r--target/linux/qualcommax/patches-6.1/0123-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch48
-rw-r--r--target/linux/qualcommax/patches-6.1/0129-arm64-dts-qcom-ipq8074-add-QFPROM-fuses.patch128
-rw-r--r--target/linux/qualcommax/patches-6.1/0130-arm64-dts-qcom-ipq8074-add-CPU-OPP-table.patch102
-rw-r--r--target/linux/qualcommax/patches-6.1/0136-remoteproc-qcom-wcss-populate-driver-data-for-IPQ601.patch61
-rw-r--r--target/linux/qualcommax/patches-6.1/0137-arm64-dts-qcom-ipq6018-add-SDHCI-node.patch45
-rw-r--r--target/linux/qualcommax/patches-6.1/0139-arm64-dts-qcom-ipq6018-add-LDOA2-regulator.patch27
-rw-r--r--target/linux/qualcommax/patches-6.1/0400-mtd-rawnand-add-support-for-TH58NYG3S0HBAI4.patch42
-rw-r--r--target/linux/qualcommax/patches-6.1/0900-power-Add-Qualcomm-APM.patch1047
-rw-r--r--target/linux/qualcommax/patches-6.1/0901-regulator-add-Qualcomm-CPR-regulators.patch12144
-rw-r--r--target/linux/qualcommax/patches-6.1/0902-arm64-dts-ipq8074-add-label-to-clocks.patch24
-rw-r--r--target/linux/qualcommax/patches-6.1/0903-psci-dont-advertise-OSI-support-for-IPQ6018.patch40
-rw-r--r--target/linux/qualcommax/patches-6.1/0904-clk-qcom-ipq6018-workaround-networking-clock-parenti.patch109
-rw-r--r--target/linux/qualcommax/patches-6.1/0905-remoteproc-q6v5_wcss-change-ssr-name-for-ipq6018-wif.patch40
-rw-r--r--target/linux/qualcommax/patches-6.1/0906-arm64-dts-qcom-ipq6018-add-wifi-node.patch120
-rw-r--r--target/linux/qualcommax/patches-6.1/0907-soc-qcom-fix-smp2p-ack-on-ipq6018.patch53
-rw-r--r--target/linux/qualcommax/patches-6.1/0908-remoteproc-qcom_q6v5_wcss-add-optional-qdss_at-clock.patch55
-rw-r--r--target/linux/qualcommax/patches-6.1/0909-arm64-dts-qcom-ipq6018-assign-QDSS_AT-clock-to-wifi-.patch26
-rw-r--r--target/linux/qualcommax/patches-6.1/0910-arm64-dts-qcom-ipq6018-change-voltage-to-perf-levels.patch65
97 files changed, 23959 insertions, 0 deletions
diff --git a/target/linux/qualcommax/config-6.1 b/target/linux/qualcommax/config-6.1
new file mode 100644
index 0000000000..acfdc99b22
--- /dev/null
+++ b/target/linux/qualcommax/config-6.1
@@ -0,0 +1,540 @@
+CONFIG_64BIT=y
+# CONFIG_APQ_GCC_8084 is not set
+# CONFIG_APQ_MMCC_8084 is not set
+CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y
+CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE=y
+CONFIG_ARCH_DMA_ADDR_T_64BIT=y
+CONFIG_ARCH_HIBERNATION_POSSIBLE=y
+CONFIG_ARCH_KEEP_MEMBLOCK=y
+CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y
+CONFIG_ARCH_MMAP_RND_BITS=18
+CONFIG_ARCH_MMAP_RND_BITS_MAX=24
+CONFIG_ARCH_MMAP_RND_BITS_MIN=18
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=11
+CONFIG_ARCH_NR_GPIO=0
+CONFIG_ARCH_PROC_KCORE_TEXT=y
+CONFIG_ARCH_QCOM=y
+CONFIG_ARCH_SPARSEMEM_ENABLE=y
+CONFIG_ARCH_STACKWALK=y
+CONFIG_ARCH_SUSPEND_POSSIBLE=y
+CONFIG_ARCH_WANTS_NO_INSTR=y
+CONFIG_ARCH_WANTS_THP_SWAP=y
+CONFIG_ARM64=y
+CONFIG_ARM64_4K_PAGES=y
+CONFIG_ARM64_ERRATUM_1165522=y
+CONFIG_ARM64_ERRATUM_1286807=y
+CONFIG_ARM64_ERRATUM_2051678=y
+CONFIG_ARM64_ERRATUM_2054223=y
+CONFIG_ARM64_ERRATUM_2067961=y
+CONFIG_ARM64_ERRATUM_2077057=y
+CONFIG_ARM64_ERRATUM_2658417=y
+CONFIG_ARM64_LD_HAS_FIX_ERRATUM_843419=y
+CONFIG_ARM64_PAGE_SHIFT=12
+CONFIG_ARM64_PA_BITS=48
+CONFIG_ARM64_PA_BITS_48=y
+CONFIG_ARM64_PTR_AUTH=y
+CONFIG_ARM64_PTR_AUTH_KERNEL=y
+CONFIG_ARM64_SME=y
+CONFIG_ARM64_SVE=y
+CONFIG_ARM64_TAGGED_ADDR_ABI=y
+CONFIG_ARM64_VA_BITS=39
+CONFIG_ARM64_VA_BITS_39=y
+CONFIG_ARM64_WORKAROUND_REPEAT_TLBI=y
+CONFIG_ARM64_WORKAROUND_SPECULATIVE_AT=y
+CONFIG_ARM64_WORKAROUND_TSB_FLUSH_FAILURE=y
+CONFIG_ARM_AMBA=y
+CONFIG_ARM_ARCH_TIMER=y
+CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y
+CONFIG_ARM_GIC=y
+CONFIG_ARM_GIC_V2M=y
+CONFIG_ARM_GIC_V3=y
+CONFIG_ARM_GIC_V3_ITS=y
+CONFIG_ARM_GIC_V3_ITS_PCI=y
+# CONFIG_ARM_MHU_V2 is not set
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_ARM_PSCI_FW=y
+# CONFIG_ARM_QCOM_CPUFREQ_HW is not set
+CONFIG_ARM_QCOM_CPUFREQ_NVMEM=y
+CONFIG_AT803X_PHY=y
+CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_SD=y
+CONFIG_BLK_MQ_PCI=y
+CONFIG_BLK_MQ_VIRTIO=y
+CONFIG_BLK_PM=y
+CONFIG_CAVIUM_TX2_ERRATUM_219=y
+CONFIG_CC_HAVE_SHADOW_CALL_STACK=y
+CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y
+CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5"
+CONFIG_CC_NO_ARRAY_BOUNDS=y
+CONFIG_CLONE_BACKWARDS=y
+CONFIG_COMMON_CLK=y
+CONFIG_COMMON_CLK_QCOM=y
+CONFIG_COMPACT_UNEVICTABLE_DEFAULT=1
+# CONFIG_COMPAT_32BIT_TIME is not set
+CONFIG_CONTEXT_TRACKING=y
+CONFIG_CONTEXT_TRACKING_IDLE=y
+CONFIG_COREDUMP=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_CPUFREQ_DT_PLATDEV=y
+CONFIG_CPU_FREQ=y
+# CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE is not set
+CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
+CONFIG_CPU_FREQ_GOV_ATTR_SET=y
+# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set
+# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+# CONFIG_CPU_FREQ_GOV_USERSPACE is not set
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_THERMAL=y
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_IDLE_GOV_MENU=y
+CONFIG_CPU_IDLE_MULTIPLE_DRIVERS=y
+CONFIG_CPU_LITTLE_ENDIAN=y
+CONFIG_CPU_PM=y
+CONFIG_CPU_RMAP=y
+CONFIG_CPU_THERMAL=y
+CONFIG_CRC16=y
+CONFIG_CRC8=y
+CONFIG_CRYPTO_AUTHENC=y
+CONFIG_CRYPTO_CBC=y
+CONFIG_CRYPTO_DEFLATE=y
+CONFIG_CRYPTO_DEV_QCE=y
+CONFIG_CRYPTO_DEV_QCE_AEAD=y
+# CONFIG_CRYPTO_DEV_QCE_ENABLE_AEAD is not set
+CONFIG_CRYPTO_DEV_QCE_ENABLE_ALL=y
+# CONFIG_CRYPTO_DEV_QCE_ENABLE_SHA is not set
+# CONFIG_CRYPTO_DEV_QCE_ENABLE_SKCIPHER is not set
+CONFIG_CRYPTO_DEV_QCE_SHA=y
+CONFIG_CRYPTO_DEV_QCE_SKCIPHER=y
+CONFIG_CRYPTO_DEV_QCE_SW_MAX_LEN=512
+CONFIG_CRYPTO_DEV_QCOM_RNG=y
+CONFIG_CRYPTO_ECB=y
+CONFIG_CRYPTO_HASH_INFO=y
+CONFIG_CRYPTO_HW=y
+CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
+CONFIG_CRYPTO_LIB_DES=y
+CONFIG_CRYPTO_LIB_SHA1=y
+CONFIG_CRYPTO_LIB_SHA256=y
+CONFIG_CRYPTO_LIB_UTILS=y
+CONFIG_CRYPTO_LZO=y
+CONFIG_CRYPTO_RNG=y
+CONFIG_CRYPTO_RNG2=y
+CONFIG_CRYPTO_SHA1=y
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_XTS=y
+CONFIG_CRYPTO_ZSTD=y
+CONFIG_DCACHE_WORD_ACCESS=y
+CONFIG_DEBUG_BUGVERBOSE=y
+CONFIG_DEBUG_INFO=y
+CONFIG_DEV_COREDUMP=y
+CONFIG_DMADEVICES=y
+CONFIG_DMA_DIRECT_REMAP=y
+CONFIG_DMA_ENGINE=y
+CONFIG_DMA_OF=y
+CONFIG_DMA_VIRTUAL_CHANNELS=y
+CONFIG_DTC=y
+CONFIG_DT_IDLE_STATES=y
+CONFIG_EDAC_SUPPORT=y
+CONFIG_EXCLUSIVE_SYSTEM_RAM=y
+CONFIG_FIXED_PHY=y
+CONFIG_FIX_EARLYCON_MEM=y
+CONFIG_FRAME_POINTER=y
+CONFIG_FUJITSU_ERRATUM_010001=y
+CONFIG_FWNODE_MDIO=y
+CONFIG_FW_LOADER_PAGED_BUF=y
+CONFIG_FW_LOADER_SYSFS=y
+CONFIG_GCC11_NO_ARRAY_BOUNDS=y
+CONFIG_GCC_ASM_GOTO_OUTPUT_WORKAROUND=y
+CONFIG_GCC_SUPPORTS_DYNAMIC_FTRACE_WITH_REGS=y
+CONFIG_GENERIC_ALLOCATOR=y
+CONFIG_GENERIC_ARCH_TOPOLOGY=y
+CONFIG_GENERIC_BUG=y
+CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y
+CONFIG_GENERIC_CLOCKEVENTS=y
+CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
+CONFIG_GENERIC_CPU_AUTOPROBE=y
+CONFIG_GENERIC_CPU_VULNERABILITIES=y
+CONFIG_GENERIC_CSUM=y
+CONFIG_GENERIC_EARLY_IOREMAP=y
+CONFIG_GENERIC_GETTIMEOFDAY=y
+CONFIG_GENERIC_IDLE_POLL_SETUP=y
+CONFIG_GENERIC_IOREMAP=y
+CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y
+CONFIG_GENERIC_IRQ_SHOW=y
+CONFIG_GENERIC_IRQ_SHOW_LEVEL=y
+CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED=y
+CONFIG_GENERIC_MSI_IRQ=y
+CONFIG_GENERIC_MSI_IRQ_DOMAIN=y
+CONFIG_GENERIC_PCI_IOMAP=y
+CONFIG_GENERIC_PHY=y
+CONFIG_GENERIC_PINCONF=y
+CONFIG_GENERIC_PINCTRL_GROUPS=y
+CONFIG_GENERIC_PINMUX_FUNCTIONS=y
+CONFIG_GENERIC_SCHED_CLOCK=y
+CONFIG_GENERIC_SMP_IDLE_THREAD=y
+CONFIG_GENERIC_STRNCPY_FROM_USER=y
+CONFIG_GENERIC_STRNLEN_USER=y
+CONFIG_GENERIC_TIME_VSYSCALL=y
+CONFIG_GLOB=y
+CONFIG_GPIOLIB_IRQCHIP=y
+CONFIG_GPIO_CDEV=y
+CONFIG_HARDIRQS_SW_RESEND=y
+CONFIG_HAS_DMA=y
+CONFIG_HAS_IOMEM=y
+CONFIG_HAS_IOPORT_MAP=y
+CONFIG_HWSPINLOCK=y
+CONFIG_HWSPINLOCK_QCOM=y
+CONFIG_I2C=y
+CONFIG_I2C_BOARDINFO=y
+CONFIG_I2C_CHARDEV=y
+CONFIG_I2C_HELPER_AUTO=y
+# CONFIG_I2C_QCOM_CCI is not set
+CONFIG_I2C_QUP=y
+CONFIG_IIO=y
+CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
+CONFIG_INITRAMFS_SOURCE=""
+CONFIG_IPQ_APSS_6018=y
+CONFIG_IPQ_APSS_PLL=y
+# CONFIG_IPQ_GCC_4019 is not set
+# CONFIG_IPQ_GCC_6018 is not set
+# CONFIG_IPQ_GCC_806X is not set
+# CONFIG_IPQ_GCC_8074 is not set
+# CONFIG_IPQ_LCC_806X is not set
+CONFIG_IRQCHIP=y
+CONFIG_IRQ_DOMAIN=y
+CONFIG_IRQ_DOMAIN_HIERARCHY=y
+CONFIG_IRQ_FASTEOI_HIERARCHY_HANDLERS=y
+CONFIG_IRQ_FORCED_THREADING=y
+CONFIG_IRQ_WORK=y
+# CONFIG_KPSS_XCC is not set
+CONFIG_LIBFDT=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_LOCK_SPIN_ON_OWNER=y
+CONFIG_LZO_COMPRESS=y
+CONFIG_LZO_DECOMPRESS=y
+CONFIG_MAILBOX=y
+# CONFIG_MAILBOX_TEST is not set
+CONFIG_MDIO_BUS=y
+CONFIG_MDIO_DEVICE=y
+CONFIG_MDIO_DEVRES=y
+CONFIG_MDIO_IPQ4019=y
+# CONFIG_MDM_GCC_9615 is not set
+# CONFIG_MDM_LCC_9615 is not set
+CONFIG_MEMFD_CREATE=y
+# CONFIG_MFD_QCOM_RPM is not set
+CONFIG_MFD_SYSCON=y
+CONFIG_MIGRATION=y
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_CQHCI=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_IO_ACCESSORS=y
+CONFIG_MMC_SDHCI_MSM=y
+# CONFIG_MMC_SDHCI_PCI is not set
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MODULES_USE_ELF_RELA=y
+# CONFIG_MSM_GCC_8660 is not set
+# CONFIG_MSM_GCC_8909 is not set
+# CONFIG_MSM_GCC_8916 is not set
+# CONFIG_MSM_GCC_8939 is not set
+# CONFIG_MSM_GCC_8960 is not set
+# CONFIG_MSM_GCC_8974 is not set
+# CONFIG_MSM_GCC_8976 is not set
+# CONFIG_MSM_GCC_8994 is not set
+# CONFIG_MSM_GCC_8996 is not set
+# CONFIG_MSM_GCC_8998 is not set
+# CONFIG_MSM_GPUCC_8998 is not set
+# CONFIG_MSM_LCC_8960 is not set
+# CONFIG_MSM_MMCC_8960 is not set
+# CONFIG_MSM_MMCC_8974 is not set
+# CONFIG_MSM_MMCC_8996 is not set
+# CONFIG_MSM_MMCC_8998 is not set
+CONFIG_MTD_NAND_CORE=y
+CONFIG_MTD_NAND_ECC=y
+CONFIG_MTD_NAND_ECC_SW_HAMMING=y
+CONFIG_MTD_NAND_QCOM=y
+CONFIG_MTD_QCOMSMEM_PARTS=y
+CONFIG_MTD_RAW_NAND=y
+CONFIG_MTD_SPI_NOR=y
+CONFIG_MTD_UBI=y
+CONFIG_MTD_UBI_BEB_LIMIT=20
+CONFIG_MTD_UBI_BLOCK=y
+CONFIG_MTD_UBI_WL_THRESHOLD=4096
+CONFIG_MUTEX_SPIN_ON_OWNER=y
+CONFIG_NEED_DMA_MAP_STATE=y
+CONFIG_NEED_SG_DMA_LENGTH=y
+CONFIG_NET_FLOW_LIMIT=y
+CONFIG_NET_SELFTESTS=y
+CONFIG_NET_SWITCHDEV=y
+CONFIG_NLS=y
+CONFIG_NO_HZ_COMMON=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NR_CPUS=4
+CONFIG_NVIDIA_CARMEL_CNP_ERRATUM=y
+CONFIG_NVMEM=y
+CONFIG_NVMEM_LAYOUTS=y
+CONFIG_NVMEM_QCOM_QFPROM=y
+# CONFIG_NVMEM_QCOM_SEC_QFPROM is not set
+CONFIG_NVMEM_SYSFS=y
+CONFIG_NVMEM_U_BOOT_ENV=y
+CONFIG_OF=y
+CONFIG_OF_ADDRESS=y
+CONFIG_OF_EARLY_FLATTREE=y
+CONFIG_OF_FLATTREE=y
+CONFIG_OF_GPIO=y
+CONFIG_OF_IRQ=y
+CONFIG_OF_KOBJ=y
+CONFIG_OF_MDIO=y
+CONFIG_PADATA=y
+CONFIG_PAGE_POOL=y
+CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
+CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
+CONFIG_PAHOLE_HAS_LANG_EXCLUDE=y
+CONFIG_PARTITION_PERCPU=y
+CONFIG_PCI=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEASPM=y
+CONFIG_PCIEASPM_DEFAULT=y
+# CONFIG_PCIEASPM_PERFORMANCE is not set
+# CONFIG_PCIEASPM_POWERSAVE is not set
+# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIE_DW=y
+CONFIG_PCIE_DW_HOST=y
+CONFIG_PCIE_PME=y
+CONFIG_PCIE_QCOM=y
+CONFIG_PCI_DOMAINS=y
+CONFIG_PCI_DOMAINS_GENERIC=y
+CONFIG_PCI_MSI=y
+CONFIG_PCI_MSI_IRQ_DOMAIN=y
+CONFIG_PGTABLE_LEVELS=3
+CONFIG_PHYLIB=y
+CONFIG_PHYLIB_LEDS=y
+CONFIG_PHYS_ADDR_T_64BIT=y
+# CONFIG_PHY_QCOM_APQ8064_SATA is not set
+# CONFIG_PHY_QCOM_EDP is not set
+# CONFIG_PHY_QCOM_IPQ4019_USB is not set
+# CONFIG_PHY_QCOM_IPQ806X_SATA is not set
+# CONFIG_PHY_QCOM_IPQ806X_USB is not set
+# CONFIG_PHY_QCOM_PCIE2 is not set
+CONFIG_PHY_QCOM_QMP=y
+CONFIG_PHY_QCOM_QUSB2=y
+# CONFIG_PHY_QCOM_USB_HS_28NM is not set
+# CONFIG_PHY_QCOM_USB_SNPS_FEMTO_V2 is not set
+# CONFIG_PHY_QCOM_USB_SS is not set
+CONFIG_PINCTRL=y
+# CONFIG_PINCTRL_IPQ6018 is not set
+# CONFIG_PINCTRL_IPQ8074 is not set
+CONFIG_PINCTRL_MSM=y
+# CONFIG_PINCTRL_MSM8916 is not set
+# CONFIG_PINCTRL_MSM8976 is not set
+# CONFIG_PINCTRL_MSM8994 is not set
+# CONFIG_PINCTRL_MSM8996 is not set
+# CONFIG_PINCTRL_MSM8998 is not set
+# CONFIG_PINCTRL_QCM2290 is not set
+# CONFIG_PINCTRL_QCOM_SSBI_PMIC is not set
+# CONFIG_PINCTRL_QCS404 is not set
+# CONFIG_PINCTRL_SC7180 is not set
+# CONFIG_PINCTRL_SC8280XP is not set
+# CONFIG_PINCTRL_SDM660 is not set
+# CONFIG_PINCTRL_SDM845 is not set
+# CONFIG_PINCTRL_SM6350 is not set
+# CONFIG_PINCTRL_SM6375 is not set
+# CONFIG_PINCTRL_SM8150 is not set
+# CONFIG_PINCTRL_SM8250 is not set
+# CONFIG_PINCTRL_SM8450 is not set
+CONFIG_PM=y
+CONFIG_PM_CLK=y
+CONFIG_PM_OPP=y
+CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y
+CONFIG_POWER_RESET=y
+# CONFIG_POWER_RESET_MSM is not set
+CONFIG_POWER_SUPPLY=y
+CONFIG_PREEMPT_NONE_BUILD=y
+CONFIG_PRINTK_TIME=y
+CONFIG_PTP_1588_CLOCK_OPTIONAL=y
+CONFIG_QCA807X_PHY=y
+CONFIG_QCA808X_PHY=y
+# CONFIG_QCM_DISPCC_2290 is not set
+# CONFIG_QCM_GCC_2290 is not set
+# CONFIG_QCOM_A53PLL is not set
+# CONFIG_QCOM_AOSS_QMP is not set
+CONFIG_QCOM_APCS_IPC=y
+# CONFIG_QCOM_APM is not set
+# CONFIG_QCOM_APR is not set
+CONFIG_QCOM_BAM_DMA=y
+# CONFIG_QCOM_CLK_APCC_MSM8996 is not set
+# CONFIG_QCOM_CLK_APCS_MSM8916 is not set
+# CONFIG_QCOM_CLK_APCS_SDX55 is not set
+# CONFIG_QCOM_COMMAND_DB is not set
+# CONFIG_QCOM_CPR is not set
+# CONFIG_QCOM_EBI2 is not set
+# CONFIG_QCOM_FASTRPC is not set
+# CONFIG_QCOM_GENI_SE is not set
+# CONFIG_QCOM_GSBI is not set
+# CONFIG_QCOM_HFPLL is not set
+# CONFIG_QCOM_ICC_BWMON is not set
+# CONFIG_QCOM_IPCC is not set
+# CONFIG_QCOM_LLCC is not set
+CONFIG_QCOM_MDT_LOADER=y
+# CONFIG_QCOM_MPM is not set
+CONFIG_QCOM_NET_PHYLIB=y
+# CONFIG_QCOM_OCMEM is not set
+# CONFIG_QCOM_PDC is not set
+CONFIG_QCOM_PIL_INFO=y
+# CONFIG_QCOM_Q6V5_ADSP is not set
+CONFIG_QCOM_Q6V5_COMMON=y
+# CONFIG_QCOM_Q6V5_MSS is not set
+# CONFIG_QCOM_Q6V5_PAS is not set
+CONFIG_QCOM_Q6V5_WCSS=y
+# CONFIG_QCOM_RMTFS_MEM is not set
+# CONFIG_QCOM_RPMH is not set
+CONFIG_QCOM_RPROC_COMMON=y
+CONFIG_QCOM_SCM=y
+# CONFIG_QCOM_SCM_DOWNLOAD_MODE_DEFAULT is not set
+# CONFIG_QCOM_SMD_RPM is not set
+CONFIG_QCOM_SMEM=y
+CONFIG_QCOM_SMEM_STATE=y
+CONFIG_QCOM_SMP2P=y
+# CONFIG_QCOM_SMSM is not set
+CONFIG_QCOM_SOCINFO=y
+# CONFIG_QCOM_SPM is not set
+# CONFIG_QCOM_STATS is not set
+# CONFIG_QCOM_SYSMON is not set
+CONFIG_QCOM_TSENS=y
+# CONFIG_QCOM_WCNSS_CTRL is not set
+# CONFIG_QCOM_WCNSS_PIL is not set
+CONFIG_QCOM_WDT=y
+# CONFIG_QCS_GCC_404 is not set
+# CONFIG_QCS_Q6SSTOP_404 is not set
+# CONFIG_QCS_TURING_404 is not set
+CONFIG_QUEUED_RWLOCKS=y
+CONFIG_QUEUED_SPINLOCKS=y
+CONFIG_RANDSTRUCT_NONE=y
+CONFIG_RAS=y
+CONFIG_RATIONAL=y
+CONFIG_REGMAP=y
+CONFIG_REGMAP_MMIO=y
+CONFIG_REGULATOR=y
+# CONFIG_REGULATOR_CPR3 is not set
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+# CONFIG_REGULATOR_VQMMC_IPQ4019 is not set
+CONFIG_RELOCATABLE=y
+CONFIG_REMOTEPROC=y
+CONFIG_REMOTEPROC_CDEV=y
+CONFIG_RESET_CONTROLLER=y
+# CONFIG_RESET_QCOM_AOSS is not set
+# CONFIG_RESET_QCOM_PDC is not set
+CONFIG_RFS_ACCEL=y
+CONFIG_RODATA_FULL_DEFAULT_ENABLED=y
+CONFIG_RPMSG=y
+CONFIG_RPMSG_CHAR=y
+# CONFIG_RPMSG_CTRL is not set
+# CONFIG_RPMSG_NS is not set
+CONFIG_RPMSG_QCOM_GLINK=y
+CONFIG_RPMSG_QCOM_GLINK_RPM=y
+CONFIG_RPMSG_QCOM_GLINK_SMEM=y
+CONFIG_RPMSG_QCOM_SMD=y
+# CONFIG_RPMSG_TTY is not set
+CONFIG_RPS=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_I2C_AND_SPI=y
+CONFIG_RWSEM_SPIN_ON_OWNER=y
+# CONFIG_SCHED_CORE is not set
+CONFIG_SCHED_MC=y
+CONFIG_SCHED_SMT=y
+CONFIG_SCHED_THERMAL_PRESSURE=y
+CONFIG_SCSI=y
+CONFIG_SCSI_COMMON=y
+# CONFIG_SCSI_LOWLEVEL is not set
+# CONFIG_SCSI_PROC_FS is not set
+# CONFIG_SC_CAMCC_7280 is not set
+# CONFIG_SC_DISPCC_7180 is not set
+# CONFIG_SC_GCC_7180 is not set
+# CONFIG_SC_GCC_8280XP is not set
+# CONFIG_SC_GPUCC_7180 is not set
+# CONFIG_SC_LPASSCC_7280 is not set
+# CONFIG_SC_LPASS_CORECC_7180 is not set
+# CONFIG_SC_LPASS_CORECC_7280 is not set
+# CONFIG_SC_MSS_7180 is not set
+# CONFIG_SC_VIDEOCC_7180 is not set
+# CONFIG_SDM_CAMCC_845 is not set
+# CONFIG_SDM_DISPCC_845 is not set
+# CONFIG_SDM_GCC_660 is not set
+# CONFIG_SDM_GCC_845 is not set
+# CONFIG_SDM_GPUCC_845 is not set
+# CONFIG_SDM_LPASSCC_845 is not set
+# CONFIG_SDM_VIDEOCC_845 is not set
+# CONFIG_SDX_GCC_65 is not set
+CONFIG_SERIAL_8250_FSL=y
+CONFIG_SERIAL_MCTRL_GPIO=y
+CONFIG_SERIAL_MSM=y
+CONFIG_SERIAL_MSM_CONSOLE=y
+CONFIG_SGL_ALLOC=y
+CONFIG_SG_POOL=y
+CONFIG_SMP=y
+# CONFIG_SM_CAMCC_8450 is not set
+# CONFIG_SM_GCC_8150 is not set
+# CONFIG_SM_GCC_8250 is not set
+# CONFIG_SM_GCC_8450 is not set
+# CONFIG_SM_GPUCC_6350 is not set
+# CONFIG_SM_GPUCC_8150 is not set
+# CONFIG_SM_GPUCC_8250 is not set
+# CONFIG_SM_GPUCC_8350 is not set
+# CONFIG_SM_VIDEOCC_8150 is not set
+# CONFIG_SM_VIDEOCC_8250 is not set
+CONFIG_SOCK_RX_QUEUE_MAPPING=y
+CONFIG_SOC_BUS=y
+CONFIG_SOFTIRQ_ON_OWN_STACK=y
+CONFIG_SPARSEMEM=y
+CONFIG_SPARSEMEM_EXTREME=y
+CONFIG_SPARSEMEM_VMEMMAP=y
+CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
+CONFIG_SPARSE_IRQ=y
+CONFIG_SPI=y
+CONFIG_SPI_MASTER=y
+CONFIG_SPI_MEM=y
+CONFIG_SPI_QUP=y
+CONFIG_SRCU=y
+CONFIG_SWIOTLB=y
+CONFIG_SWPHY=y
+CONFIG_SYSCTL_EXCEPTION_TRACE=y
+CONFIG_THERMAL=y
+CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y
+CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0
+CONFIG_THERMAL_GOV_STEP_WISE=y
+CONFIG_THERMAL_OF=y
+CONFIG_THREAD_INFO_IN_TASK=y
+CONFIG_TICK_CPU_ACCOUNTING=y
+CONFIG_TIMER_OF=y
+CONFIG_TIMER_PROBE=y
+CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y
+CONFIG_TREE_RCU=y
+CONFIG_TREE_SRCU=y
+CONFIG_UBIFS_FS=y
+CONFIG_UBIFS_FS_ADVANCED_COMPR=y
+# CONFIG_UCLAMP_TASK is not set
+CONFIG_UNMAP_KERNEL_AT_EL0=y
+CONFIG_USB=y
+CONFIG_USB_COMMON=y
+CONFIG_USB_SUPPORT=y
+CONFIG_VIRTIO=y
+CONFIG_VIRTIO_ANCHOR=y
+# CONFIG_VIRTIO_BLK is not set
+# CONFIG_VIRTIO_NET is not set
+CONFIG_VMAP_STACK=y
+CONFIG_WANT_DEV_COREDUMP=y
+CONFIG_WATCHDOG_CORE=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_XPS=y
+CONFIG_XXHASH=y
+CONFIG_ZLIB_DEFLATE=y
+CONFIG_ZLIB_INFLATE=y
+CONFIG_ZONE_DMA32=y
+CONFIG_ZSTD_COMMON=y
+CONFIG_ZSTD_COMPRESS=y
+CONFIG_ZSTD_DECOMPRESS=y
diff --git a/target/linux/qualcommax/patches-6.1/0001-v6.2-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch b/target/linux/qualcommax/patches-6.1/0001-v6.2-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch
new file mode 100644
index 0000000000..5a4b1bbc99
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0001-v6.2-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch
@@ -0,0 +1,32 @@
+From 6463c10bfdbd684ec7ecfd408ea541283215a088 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:06:28 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add A53 PLL node
+
+Add the required node for A53 PLL which will be used to provide the CPU
+clock via APCS for APSS scaling.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220818220628.339366-9-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -677,6 +677,14 @@
+ #mbox-cells = <1>;
+ };
+
++ a53pll: clock@b116000 {
++ compatible = "qcom,ipq8074-a53pll";
++ reg = <0x0b116000 0x40>;
++ #clock-cells = <0>;
++ clocks = <&xo>;
++ clock-names = "xo";
++ };
++
+ timer@b120000 {
+ #address-cells = <1>;
+ #size-cells = <1>;
diff --git a/target/linux/qualcommax/patches-6.1/0002-v6.2-thermal-drivers-tsens-Add-support-for-combined-inter.patch b/target/linux/qualcommax/patches-6.1/0002-v6.2-thermal-drivers-tsens-Add-support-for-combined-inter.patch
new file mode 100644
index 0000000000..0320725999
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0002-v6.2-thermal-drivers-tsens-Add-support-for-combined-inter.patch
@@ -0,0 +1,134 @@
+From e593e834fe8ba9bf314d8215ac05d8787f81efda Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:02:42 +0200
+Subject: [PATCH] thermal/drivers/tsens: Add support for combined interrupt
+
+Despite using tsens v2.3 IP, IPQ8074 and IPQ6018 only have one IRQ for
+signaling both up/low and critical trips.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220818220245.338396-2-robimarko@gmail.com
+Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
+---
+ drivers/thermal/qcom/tsens-8960.c | 1 +
+ drivers/thermal/qcom/tsens-v0_1.c | 1 +
+ drivers/thermal/qcom/tsens-v1.c | 1 +
+ drivers/thermal/qcom/tsens-v2.c | 1 +
+ drivers/thermal/qcom/tsens.c | 38 ++++++++++++++++++++++++++-----
+ drivers/thermal/qcom/tsens.h | 2 ++
+ 6 files changed, 38 insertions(+), 6 deletions(-)
+
+--- a/drivers/thermal/qcom/tsens-8960.c
++++ b/drivers/thermal/qcom/tsens-8960.c
+@@ -269,6 +269,7 @@ static const struct tsens_ops ops_8960 =
+ static struct tsens_features tsens_8960_feat = {
+ .ver_major = VER_0,
+ .crit_int = 0,
++ .combo_int = 0,
+ .adc = 1,
+ .srot_split = 0,
+ .max_sensors = 11,
+--- a/drivers/thermal/qcom/tsens-v0_1.c
++++ b/drivers/thermal/qcom/tsens-v0_1.c
+@@ -549,6 +549,7 @@ static int __init init_8939(struct tsens
+ static struct tsens_features tsens_v0_1_feat = {
+ .ver_major = VER_0_1,
+ .crit_int = 0,
++ .combo_int = 0,
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
+--- a/drivers/thermal/qcom/tsens-v1.c
++++ b/drivers/thermal/qcom/tsens-v1.c
+@@ -273,6 +273,7 @@ static int calibrate_8976(struct tsens_p
+ static struct tsens_features tsens_v1_feat = {
+ .ver_major = VER_1_X,
+ .crit_int = 0,
++ .combo_int = 0,
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
+--- a/drivers/thermal/qcom/tsens-v2.c
++++ b/drivers/thermal/qcom/tsens-v2.c
+@@ -31,6 +31,7 @@
+ static struct tsens_features tsens_v2_feat = {
+ .ver_major = VER_2_X,
+ .crit_int = 1,
++ .combo_int = 0,
+ .adc = 0,
+ .srot_split = 1,
+ .max_sensors = 16,
+--- a/drivers/thermal/qcom/tsens.c
++++ b/drivers/thermal/qcom/tsens.c
+@@ -532,6 +532,27 @@ static irqreturn_t tsens_irq_thread(int
+ return IRQ_HANDLED;
+ }
+
++/**
++ * tsens_combined_irq_thread() - Threaded interrupt handler for combined interrupts
++ * @irq: irq number
++ * @data: tsens controller private data
++ *
++ * Handle the combined interrupt as if it were 2 separate interrupts, so call the
++ * critical handler first and then the up/low one.
++ *
++ * Return: IRQ_HANDLED
++ */
++static irqreturn_t tsens_combined_irq_thread(int irq, void *data)
++{
++ irqreturn_t ret;
++
++ ret = tsens_critical_irq_thread(irq, data);
++ if (ret != IRQ_HANDLED)
++ return ret;
++
++ return tsens_irq_thread(irq, data);
++}
++
+ static int tsens_set_trips(struct thermal_zone_device *tz, int low, int high)
+ {
+ struct tsens_sensor *s = tz->devdata;
+@@ -1074,13 +1095,18 @@ static int tsens_register(struct tsens_p
+ tsens_mC_to_hw(priv->sensor, 0));
+ }
+
+- ret = tsens_register_irq(priv, "uplow", tsens_irq_thread);
+- if (ret < 0)
+- return ret;
++ if (priv->feat->combo_int) {
++ ret = tsens_register_irq(priv, "combined",
++ tsens_combined_irq_thread);
++ } else {
++ ret = tsens_register_irq(priv, "uplow", tsens_irq_thread);
++ if (ret < 0)
++ return ret;
+
+- if (priv->feat->crit_int)
+- ret = tsens_register_irq(priv, "critical",
+- tsens_critical_irq_thread);
++ if (priv->feat->crit_int)
++ ret = tsens_register_irq(priv, "critical",
++ tsens_critical_irq_thread);
++ }
+
+ return ret;
+ }
+--- a/drivers/thermal/qcom/tsens.h
++++ b/drivers/thermal/qcom/tsens.h
+@@ -493,6 +493,7 @@ enum regfield_ids {
+ * struct tsens_features - Features supported by the IP
+ * @ver_major: Major number of IP version
+ * @crit_int: does the IP support critical interrupts?
++ * @combo_int: does the IP use one IRQ for up, low and critical thresholds?
+ * @adc: do the sensors only output adc code (instead of temperature)?
+ * @srot_split: does the IP neatly splits the register space into SROT and TM,
+ * with SROT only being available to secure boot firmware?
+@@ -502,6 +503,7 @@ enum regfield_ids {
+ struct tsens_features {
+ unsigned int ver_major;
+ unsigned int crit_int:1;
++ unsigned int combo_int:1;
+ unsigned int adc:1;
+ unsigned int srot_split:1;
+ unsigned int has_watchdog:1;
diff --git a/target/linux/qualcommax/patches-6.1/0003-v6.2-thermal-drivers-tsens-Allow-configuring-min-and-max-.patch b/target/linux/qualcommax/patches-6.1/0003-v6.2-thermal-drivers-tsens-Allow-configuring-min-and-max-.patch
new file mode 100644
index 0000000000..363061813a
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0003-v6.2-thermal-drivers-tsens-Allow-configuring-min-and-max-.patch
@@ -0,0 +1,101 @@
+From 7805365fee582056b32c69cf35aafbb94b14a8ca Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:02:43 +0200
+Subject: [PATCH] thermal/drivers/tsens: Allow configuring min and max trips
+
+IPQ8074 and IPQ6018 dont support negative trip temperatures and support
+up to 204 degrees C as the max trip temperature.
+
+So, instead of always setting the -40 as min and 120 degrees C as max
+allow it to be configured as part of the features.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>
+Link: https://lore.kernel.org/r/20220818220245.338396-3-robimarko@gmail.com
+Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
+---
+ drivers/thermal/qcom/tsens-8960.c | 2 ++
+ drivers/thermal/qcom/tsens-v0_1.c | 2 ++
+ drivers/thermal/qcom/tsens-v1.c | 2 ++
+ drivers/thermal/qcom/tsens-v2.c | 2 ++
+ drivers/thermal/qcom/tsens.c | 4 ++--
+ drivers/thermal/qcom/tsens.h | 4 ++++
+ 6 files changed, 14 insertions(+), 2 deletions(-)
+
+--- a/drivers/thermal/qcom/tsens-8960.c
++++ b/drivers/thermal/qcom/tsens-8960.c
+@@ -273,6 +273,8 @@ static struct tsens_features tsens_8960_
+ .adc = 1,
+ .srot_split = 0,
+ .max_sensors = 11,
++ .trip_min_temp = -40000,
++ .trip_max_temp = 120000,
+ };
+
+ struct tsens_plat_data data_8960 = {
+--- a/drivers/thermal/qcom/tsens-v0_1.c
++++ b/drivers/thermal/qcom/tsens-v0_1.c
+@@ -553,6 +553,8 @@ static struct tsens_features tsens_v0_1_
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
++ .trip_min_temp = -40000,
++ .trip_max_temp = 120000,
+ };
+
+ static const struct reg_field tsens_v0_1_regfields[MAX_REGFIELDS] = {
+--- a/drivers/thermal/qcom/tsens-v1.c
++++ b/drivers/thermal/qcom/tsens-v1.c
+@@ -277,6 +277,8 @@ static struct tsens_features tsens_v1_fe
+ .adc = 1,
+ .srot_split = 1,
+ .max_sensors = 11,
++ .trip_min_temp = -40000,
++ .trip_max_temp = 120000,
+ };
+
+ static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = {
+--- a/drivers/thermal/qcom/tsens-v2.c
++++ b/drivers/thermal/qcom/tsens-v2.c
+@@ -35,6 +35,8 @@ static struct tsens_features tsens_v2_fe
+ .adc = 0,
+ .srot_split = 1,
+ .max_sensors = 16,
++ .trip_min_temp = -40000,
++ .trip_max_temp = 120000,
+ };
+
+ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
+--- a/drivers/thermal/qcom/tsens.c
++++ b/drivers/thermal/qcom/tsens.c
+@@ -573,8 +573,8 @@ static int tsens_set_trips(struct therma
+ dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n",
+ hw_id, __func__, low, high);
+
+- cl_high = clamp_val(high, -40000, 120000);
+- cl_low = clamp_val(low, -40000, 120000);
++ cl_high = clamp_val(high, priv->feat->trip_min_temp, priv->feat->trip_max_temp);
++ cl_low = clamp_val(low, priv->feat->trip_min_temp, priv->feat->trip_max_temp);
+
+ high_val = tsens_mC_to_hw(s, cl_high);
+ low_val = tsens_mC_to_hw(s, cl_low);
+--- a/drivers/thermal/qcom/tsens.h
++++ b/drivers/thermal/qcom/tsens.h
+@@ -499,6 +499,8 @@ enum regfield_ids {
+ * with SROT only being available to secure boot firmware?
+ * @has_watchdog: does this IP support watchdog functionality?
+ * @max_sensors: maximum sensors supported by this version of the IP
++ * @trip_min_temp: minimum trip temperature supported by this version of the IP
++ * @trip_max_temp: maximum trip temperature supported by this version of the IP
+ */
+ struct tsens_features {
+ unsigned int ver_major;
+@@ -508,6 +510,8 @@ struct tsens_features {
+ unsigned int srot_split:1;
+ unsigned int has_watchdog:1;
+ unsigned int max_sensors;
++ int trip_min_temp;
++ int trip_max_temp;
+ };
+
+ /**
diff --git a/target/linux/qualcommax/patches-6.1/0004-v6.2-thermal-drivers-tsens-Add-IPQ8074-support.patch b/target/linux/qualcommax/patches-6.1/0004-v6.2-thermal-drivers-tsens-Add-IPQ8074-support.patch
new file mode 100644
index 0000000000..eaea693959
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0004-v6.2-thermal-drivers-tsens-Add-IPQ8074-support.patch
@@ -0,0 +1,74 @@
+From 0164d794cbc58488a7321272e95958d10cf103a4 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:02:44 +0200
+Subject: [PATCH] thermal/drivers/tsens: Add IPQ8074 support
+
+Qualcomm IPQ8074 uses tsens v2.3 IP, however unlike other tsens v2 IP
+it only has one IRQ, that is used for up/low as well as critical.
+It also does not support negative trip temperatures.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>
+Link: https://lore.kernel.org/r/20220818220245.338396-4-robimarko@gmail.com
+Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
+---
+ drivers/thermal/qcom/tsens-v2.c | 17 +++++++++++++++++
+ drivers/thermal/qcom/tsens.c | 3 +++
+ drivers/thermal/qcom/tsens.h | 2 +-
+ 3 files changed, 21 insertions(+), 1 deletion(-)
+
+--- a/drivers/thermal/qcom/tsens-v2.c
++++ b/drivers/thermal/qcom/tsens-v2.c
+@@ -39,6 +39,17 @@ static struct tsens_features tsens_v2_fe
+ .trip_max_temp = 120000,
+ };
+
++static struct tsens_features ipq8074_feat = {
++ .ver_major = VER_2_X,
++ .crit_int = 1,
++ .combo_int = 1,
++ .adc = 0,
++ .srot_split = 1,
++ .max_sensors = 16,
++ .trip_min_temp = 0,
++ .trip_max_temp = 204000,
++};
++
+ static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = {
+ /* ----- SROT ------ */
+ /* VERSION */
+@@ -104,6 +115,12 @@ struct tsens_plat_data data_tsens_v2 = {
+ .fields = tsens_v2_regfields,
+ };
+
++struct tsens_plat_data data_ipq8074 = {
++ .ops = &ops_generic_v2,
++ .feat = &ipq8074_feat,
++ .fields = tsens_v2_regfields,
++};
++
+ /* Kept around for backward compatibility with old msm8996.dtsi */
+ struct tsens_plat_data data_8996 = {
+ .num_sensors = 13,
+--- a/drivers/thermal/qcom/tsens.c
++++ b/drivers/thermal/qcom/tsens.c
+@@ -981,6 +981,9 @@ static const struct of_device_id tsens_t
+ .compatible = "qcom,ipq8064-tsens",
+ .data = &data_8960,
+ }, {
++ .compatible = "qcom,ipq8074-tsens",
++ .data = &data_ipq8074,
++ }, {
+ .compatible = "qcom,mdm9607-tsens",
+ .data = &data_9607,
+ }, {
+--- a/drivers/thermal/qcom/tsens.h
++++ b/drivers/thermal/qcom/tsens.h
+@@ -597,6 +597,6 @@ extern struct tsens_plat_data data_8916,
+ extern struct tsens_plat_data data_tsens_v1, data_8976, data_8956;
+
+ /* TSENS v2 targets */
+-extern struct tsens_plat_data data_8996, data_tsens_v2;
++extern struct tsens_plat_data data_8996, data_ipq8074, data_tsens_v2;
+
+ #endif /* __QCOM_TSENS_H__ */
diff --git a/target/linux/qualcommax/patches-6.1/0005-v6.2-arm64-dts-qcom-ipq8074-add-thermal-nodes.patch b/target/linux/qualcommax/patches-6.1/0005-v6.2-arm64-dts-qcom-ipq8074-add-thermal-nodes.patch
new file mode 100644
index 0000000000..f5abd27965
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0005-v6.2-arm64-dts-qcom-ipq8074-add-thermal-nodes.patch
@@ -0,0 +1,130 @@
+From c3cc0c2a17f552be2426200e47a9e2c62cf449ce Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:02:45 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add thermal nodes
+
+IPQ8074 has a tsens v2.3.0 peripheral which monitors
+temperatures around the various subsystems on the
+die.
+
+So lets add the tsens and thermal zone nodes, passive
+CPU cooling will come in later patches after CPU frequency
+scaling is supported.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220818220245.338396-5-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 96 +++++++++++++++++++++++++++
+ 1 file changed, 96 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -276,6 +276,16 @@
+ status = "disabled";
+ };
+
++ tsens: thermal-sensor@4a9000 {
++ compatible = "qcom,ipq8074-tsens";
++ reg = <0x4a9000 0x1000>, /* TM */
++ <0x4a8000 0x1000>; /* SROT */
++ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "combined";
++ #qcom,sensors = <16>;
++ #thermal-sensor-cells = <1>;
++ };
++
+ cryptobam: dma-controller@704000 {
+ compatible = "qcom,bam-v1.7.0";
+ reg = <0x00704000 0x20000>;
+@@ -876,4 +886,90 @@
+ <GIC_PPI 4 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+ <GIC_PPI 1 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
+ };
++
++ thermal-zones {
++ nss-top-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 4>;
++ };
++
++ nss0-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 5>;
++ };
++
++ nss1-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 6>;
++ };
++
++ wcss-phya0-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 7>;
++ };
++
++ wcss-phya1-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 8>;
++ };
++
++ cpu0_thermal: cpu0-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 9>;
++ };
++
++ cpu1_thermal: cpu1-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 10>;
++ };
++
++ cpu2_thermal: cpu2-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 11>;
++ };
++
++ cpu3_thermal: cpu3-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 12>;
++ };
++
++ cluster_thermal: cluster-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 13>;
++ };
++
++ wcss-phyb0-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 14>;
++ };
++
++ wcss-phyb1-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++
++ thermal-sensors = <&tsens 15>;
++ };
++ };
+ };
diff --git a/target/linux/qualcommax/patches-6.1/0006-v6.2-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch b/target/linux/qualcommax/patches-6.1/0006-v6.2-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch
new file mode 100644
index 0000000000..96b49e60bf
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0006-v6.2-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch
@@ -0,0 +1,29 @@
+From 0df592a0a1a3fff9133977192677aa915afc174f Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:08:49 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add clocks to APCS
+
+APCS now has support for providing the APSS clocks as the child device
+for IPQ8074.
+
+So, add the A53 PLL and XO clocks in order to use APCS as the CPU
+clocksource for APSS scaling.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220818220849.339732-4-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -682,6 +682,8 @@
+ apcs_glb: mailbox@b111000 {
+ compatible = "qcom,ipq8074-apcs-apps-global";
+ reg = <0x0b111000 0x1000>;
++ clocks = <&a53pll>, <&xo>;
++ clock-names = "pll", "xo";
+
+ #clock-cells = <1>;
+ #mbox-cells = <1>;
diff --git a/target/linux/qualcommax/patches-6.1/0007-v6.2-clk-qcom-ipq8074-convert-to-parent-data.patch b/target/linux/qualcommax/patches-6.1/0007-v6.2-clk-qcom-ipq8074-convert-to-parent-data.patch
new file mode 100644
index 0000000000..c209adbc06
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0007-v6.2-clk-qcom-ipq8074-convert-to-parent-data.patch
@@ -0,0 +1,3601 @@
+From e6c5115d6845f25eda7e162dcd783a2044215867 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sun, 30 Oct 2022 18:57:01 +0100
+Subject: [PATCH] clk: qcom: ipq8074: convert to parent data
+
+Convert the IPQ8074 GCC driver to use parent data instead of global
+name matching.
+
+Utilize ARRAY_SIZE for num_parents instead of hardcoding the value.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221030175703.1103224-1-robimarko@gmail.com
+---
+ drivers/clk/qcom/gcc-ipq8074.c | 1781 +++++++++++++++-----------------
+ 1 file changed, 813 insertions(+), 968 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq8074.c
++++ b/drivers/clk/qcom/gcc-ipq8074.c
+@@ -49,349 +49,6 @@ enum {
+ P_UNIPHY2_TX,
+ };
+
+-static const char * const gcc_xo_gpll0_gpll0_out_main_div2[] = {
+- "xo",
+- "gpll0",
+- "gpll0_out_main_div2",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_gpll0_out_main_div2_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+- { P_GPLL0_DIV2, 4 },
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+-};
+-
+-static const char * const gcc_xo_gpll0_gpll2_gpll0_out_main_div2[] = {
+- "xo",
+- "gpll0",
+- "gpll2",
+- "gpll0_out_main_div2",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_gpll2_gpll0_out_main_div2_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+- { P_GPLL2, 2 },
+- { P_GPLL0_DIV2, 4 },
+-};
+-
+-static const char * const gcc_xo_gpll0_sleep_clk[] = {
+- "xo",
+- "gpll0",
+- "sleep_clk",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_sleep_clk_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 2 },
+- { P_SLEEP_CLK, 6 },
+-};
+-
+-static const char * const gcc_xo_gpll6_gpll0_gpll0_out_main_div2[] = {
+- "xo",
+- "gpll6",
+- "gpll0",
+- "gpll0_out_main_div2",
+-};
+-
+-static const struct parent_map gcc_xo_gpll6_gpll0_gpll0_out_main_div2_map[] = {
+- { P_XO, 0 },
+- { P_GPLL6, 1 },
+- { P_GPLL0, 3 },
+- { P_GPLL0_DIV2, 4 },
+-};
+-
+-static const char * const gcc_xo_gpll0_out_main_div2_gpll0[] = {
+- "xo",
+- "gpll0_out_main_div2",
+- "gpll0",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_out_main_div2_gpll0_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0_DIV2, 2 },
+- { P_GPLL0, 1 },
+-};
+-
+-static const char * const gcc_usb3phy_0_cc_pipe_clk_xo[] = {
+- "usb3phy_0_cc_pipe_clk",
+- "xo",
+-};
+-
+-static const struct parent_map gcc_usb3phy_0_cc_pipe_clk_xo_map[] = {
+- { P_USB3PHY_0_PIPE, 0 },
+- { P_XO, 2 },
+-};
+-
+-static const char * const gcc_usb3phy_1_cc_pipe_clk_xo[] = {
+- "usb3phy_1_cc_pipe_clk",
+- "xo",
+-};
+-
+-static const struct parent_map gcc_usb3phy_1_cc_pipe_clk_xo_map[] = {
+- { P_USB3PHY_1_PIPE, 0 },
+- { P_XO, 2 },
+-};
+-
+-static const char * const gcc_pcie20_phy0_pipe_clk_xo[] = {
+- "pcie20_phy0_pipe_clk",
+- "xo",
+-};
+-
+-static const struct parent_map gcc_pcie20_phy0_pipe_clk_xo_map[] = {
+- { P_PCIE20_PHY0_PIPE, 0 },
+- { P_XO, 2 },
+-};
+-
+-static const char * const gcc_pcie20_phy1_pipe_clk_xo[] = {
+- "pcie20_phy1_pipe_clk",
+- "xo",
+-};
+-
+-static const struct parent_map gcc_pcie20_phy1_pipe_clk_xo_map[] = {
+- { P_PCIE20_PHY1_PIPE, 0 },
+- { P_XO, 2 },
+-};
+-
+-static const char * const gcc_xo_gpll0_gpll6_gpll0_div2[] = {
+- "xo",
+- "gpll0",
+- "gpll6",
+- "gpll0_out_main_div2",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_gpll6_gpll0_div2_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+- { P_GPLL6, 2 },
+- { P_GPLL0_DIV2, 4 },
+-};
+-
+-static const char * const gcc_xo_gpll0_gpll6_gpll0_out_main_div2[] = {
+- "xo",
+- "gpll0",
+- "gpll6",
+- "gpll0_out_main_div2",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_gpll6_gpll0_out_main_div2_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+- { P_GPLL6, 2 },
+- { P_GPLL0_DIV2, 3 },
+-};
+-
+-static const char * const gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2[] = {
+- "xo",
+- "bias_pll_nss_noc_clk",
+- "gpll0",
+- "gpll2",
+-};
+-
+-static const struct parent_map gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2_map[] = {
+- { P_XO, 0 },
+- { P_BIAS_PLL_NSS_NOC, 1 },
+- { P_GPLL0, 2 },
+- { P_GPLL2, 3 },
+-};
+-
+-static const char * const gcc_xo_nss_crypto_pll_gpll0[] = {
+- "xo",
+- "nss_crypto_pll",
+- "gpll0",
+-};
+-
+-static const struct parent_map gcc_xo_nss_crypto_pll_gpll0_map[] = {
+- { P_XO, 0 },
+- { P_NSS_CRYPTO_PLL, 1 },
+- { P_GPLL0, 2 },
+-};
+-
+-static const char * const gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6[] = {
+- "xo",
+- "ubi32_pll",
+- "gpll0",
+- "gpll2",
+- "gpll4",
+- "gpll6",
+-};
+-
+-static const struct parent_map gcc_xo_ubi32_gpll0_gpll2_gpll4_gpll6_map[] = {
+- { P_XO, 0 },
+- { P_UBI32_PLL, 1 },
+- { P_GPLL0, 2 },
+- { P_GPLL2, 3 },
+- { P_GPLL4, 4 },
+- { P_GPLL6, 5 },
+-};
+-
+-static const char * const gcc_xo_gpll0_out_main_div2[] = {
+- "xo",
+- "gpll0_out_main_div2",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_out_main_div2_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0_DIV2, 1 },
+-};
+-
+-static const char * const gcc_xo_bias_gpll0_gpll4_nss_ubi32[] = {
+- "xo",
+- "bias_pll_cc_clk",
+- "gpll0",
+- "gpll4",
+- "nss_crypto_pll",
+- "ubi32_pll",
+-};
+-
+-static const struct parent_map gcc_xo_bias_gpll0_gpll4_nss_ubi32_map[] = {
+- { P_XO, 0 },
+- { P_BIAS_PLL, 1 },
+- { P_GPLL0, 2 },
+- { P_GPLL4, 3 },
+- { P_NSS_CRYPTO_PLL, 4 },
+- { P_UBI32_PLL, 5 },
+-};
+-
+-static const char * const gcc_xo_gpll0_gpll4[] = {
+- "xo",
+- "gpll0",
+- "gpll4",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_gpll4_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+- { P_GPLL4, 2 },
+-};
+-
+-static const char * const gcc_xo_uniphy0_rx_tx_ubi32_bias[] = {
+- "xo",
+- "uniphy0_gcc_rx_clk",
+- "uniphy0_gcc_tx_clk",
+- "ubi32_pll",
+- "bias_pll_cc_clk",
+-};
+-
+-static const struct parent_map gcc_xo_uniphy0_rx_tx_ubi32_bias_map[] = {
+- { P_XO, 0 },
+- { P_UNIPHY0_RX, 1 },
+- { P_UNIPHY0_TX, 2 },
+- { P_UBI32_PLL, 5 },
+- { P_BIAS_PLL, 6 },
+-};
+-
+-static const char * const gcc_xo_uniphy0_tx_rx_ubi32_bias[] = {
+- "xo",
+- "uniphy0_gcc_tx_clk",
+- "uniphy0_gcc_rx_clk",
+- "ubi32_pll",
+- "bias_pll_cc_clk",
+-};
+-
+-static const struct parent_map gcc_xo_uniphy0_tx_rx_ubi32_bias_map[] = {
+- { P_XO, 0 },
+- { P_UNIPHY0_TX, 1 },
+- { P_UNIPHY0_RX, 2 },
+- { P_UBI32_PLL, 5 },
+- { P_BIAS_PLL, 6 },
+-};
+-
+-static const char * const gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias[] = {
+- "xo",
+- "uniphy0_gcc_rx_clk",
+- "uniphy0_gcc_tx_clk",
+- "uniphy1_gcc_rx_clk",
+- "uniphy1_gcc_tx_clk",
+- "ubi32_pll",
+- "bias_pll_cc_clk",
+-};
+-
+-static const struct parent_map
+-gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias_map[] = {
+- { P_XO, 0 },
+- { P_UNIPHY0_RX, 1 },
+- { P_UNIPHY0_TX, 2 },
+- { P_UNIPHY1_RX, 3 },
+- { P_UNIPHY1_TX, 4 },
+- { P_UBI32_PLL, 5 },
+- { P_BIAS_PLL, 6 },
+-};
+-
+-static const char * const gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias[] = {
+- "xo",
+- "uniphy0_gcc_tx_clk",
+- "uniphy0_gcc_rx_clk",
+- "uniphy1_gcc_tx_clk",
+- "uniphy1_gcc_rx_clk",
+- "ubi32_pll",
+- "bias_pll_cc_clk",
+-};
+-
+-static const struct parent_map
+-gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias_map[] = {
+- { P_XO, 0 },
+- { P_UNIPHY0_TX, 1 },
+- { P_UNIPHY0_RX, 2 },
+- { P_UNIPHY1_TX, 3 },
+- { P_UNIPHY1_RX, 4 },
+- { P_UBI32_PLL, 5 },
+- { P_BIAS_PLL, 6 },
+-};
+-
+-static const char * const gcc_xo_uniphy2_rx_tx_ubi32_bias[] = {
+- "xo",
+- "uniphy2_gcc_rx_clk",
+- "uniphy2_gcc_tx_clk",
+- "ubi32_pll",
+- "bias_pll_cc_clk",
+-};
+-
+-static const struct parent_map gcc_xo_uniphy2_rx_tx_ubi32_bias_map[] = {
+- { P_XO, 0 },
+- { P_UNIPHY2_RX, 1 },
+- { P_UNIPHY2_TX, 2 },
+- { P_UBI32_PLL, 5 },
+- { P_BIAS_PLL, 6 },
+-};
+-
+-static const char * const gcc_xo_uniphy2_tx_rx_ubi32_bias[] = {
+- "xo",
+- "uniphy2_gcc_tx_clk",
+- "uniphy2_gcc_rx_clk",
+- "ubi32_pll",
+- "bias_pll_cc_clk",
+-};
+-
+-static const struct parent_map gcc_xo_uniphy2_tx_rx_ubi32_bias_map[] = {
+- { P_XO, 0 },
+- { P_UNIPHY2_TX, 1 },
+- { P_UNIPHY2_RX, 2 },
+- { P_UBI32_PLL, 5 },
+- { P_BIAS_PLL, 6 },
+-};
+-
+-static const char * const gcc_xo_gpll0_gpll6_gpll0_sleep_clk[] = {
+- "xo",
+- "gpll0",
+- "gpll6",
+- "gpll0_out_main_div2",
+- "sleep_clk",
+-};
+-
+-static const struct parent_map gcc_xo_gpll0_gpll6_gpll0_sleep_clk_map[] = {
+- { P_XO, 0 },
+- { P_GPLL0, 1 },
+- { P_GPLL6, 2 },
+- { P_GPLL0_DIV2, 4 },
+- { P_SLEEP_CLK, 6 },
+-};
+-
+ static struct clk_alpha_pll gpll0_main = {
+ .offset = 0x21000,
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT],
+@@ -400,8 +57,9 @@ static struct clk_alpha_pll gpll0_main =
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gpll0_main",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_ops,
+@@ -414,9 +72,8 @@ static struct clk_fixed_factor gpll0_out
+ .div = 2,
+ .hw.init = &(struct clk_init_data){
+ .name = "gpll0_out_main_div2",
+- .parent_names = (const char *[]){
+- "gpll0_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gpll0_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ },
+@@ -428,9 +85,8 @@ static struct clk_alpha_pll_postdiv gpll
+ .width = 4,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gpll0",
+- .parent_names = (const char *[]){
+- "gpll0_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gpll0_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_postdiv_ro_ops,
+ },
+@@ -444,8 +100,9 @@ static struct clk_alpha_pll gpll2_main =
+ .enable_mask = BIT(2),
+ .hw.init = &(struct clk_init_data){
+ .name = "gpll2_main",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_ops,
+@@ -460,9 +117,8 @@ static struct clk_alpha_pll_postdiv gpll
+ .width = 4,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gpll2",
+- .parent_names = (const char *[]){
+- "gpll2_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gpll2_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_postdiv_ro_ops,
+ },
+@@ -476,8 +132,9 @@ static struct clk_alpha_pll gpll4_main =
+ .enable_mask = BIT(5),
+ .hw.init = &(struct clk_init_data){
+ .name = "gpll4_main",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_ops,
+@@ -492,9 +149,8 @@ static struct clk_alpha_pll_postdiv gpll
+ .width = 4,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gpll4",
+- .parent_names = (const char *[]){
+- "gpll4_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gpll4_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_postdiv_ro_ops,
+ },
+@@ -509,8 +165,9 @@ static struct clk_alpha_pll gpll6_main =
+ .enable_mask = BIT(7),
+ .hw.init = &(struct clk_init_data){
+ .name = "gpll6_main",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_ops,
+@@ -525,9 +182,8 @@ static struct clk_alpha_pll_postdiv gpll
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gpll6",
+- .parent_names = (const char *[]){
+- "gpll6_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gpll6_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_postdiv_ro_ops,
+ },
+@@ -538,9 +194,8 @@ static struct clk_fixed_factor gpll6_out
+ .div = 2,
+ .hw.init = &(struct clk_init_data){
+ .name = "gpll6_out_main_div2",
+- .parent_names = (const char *[]){
+- "gpll6_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gpll6_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ },
+@@ -555,8 +210,9 @@ static struct clk_alpha_pll ubi32_pll_ma
+ .enable_mask = BIT(6),
+ .hw.init = &(struct clk_init_data){
+ .name = "ubi32_pll_main",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_huayra_ops,
+@@ -570,9 +226,8 @@ static struct clk_alpha_pll_postdiv ubi3
+ .width = 2,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "ubi32_pll",
+- .parent_names = (const char *[]){
+- "ubi32_pll_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &ubi32_pll_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_postdiv_ro_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -587,8 +242,9 @@ static struct clk_alpha_pll nss_crypto_p
+ .enable_mask = BIT(4),
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_crypto_pll_main",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_ops,
+@@ -602,9 +258,8 @@ static struct clk_alpha_pll_postdiv nss_
+ .width = 4,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_crypto_pll",
+- .parent_names = (const char *[]){
+- "nss_crypto_pll_main"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_crypto_pll_main.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_postdiv_ro_ops,
+ },
+@@ -617,6 +272,18 @@ static const struct freq_tbl ftbl_pcnoc_
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_gpll0_out_main_div2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw},
++ { .hw = &gpll0_out_main_div2.hw},
++};
++
++static const struct parent_map gcc_xo_gpll0_gpll0_out_main_div2_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++ { P_GPLL0_DIV2, 4 },
++};
++
+ static struct clk_rcg2 pcnoc_bfdcd_clk_src = {
+ .cmd_rcgr = 0x27000,
+ .freq_tbl = ftbl_pcnoc_bfdcd_clk_src,
+@@ -624,8 +291,8 @@ static struct clk_rcg2 pcnoc_bfdcd_clk_s
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pcnoc_bfdcd_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ .flags = CLK_IS_CRITICAL,
+ },
+@@ -636,9 +303,8 @@ static struct clk_fixed_factor pcnoc_clk
+ .div = 1,
+ .hw.init = &(struct clk_init_data){
+ .name = "pcnoc_clk_src",
+- .parent_names = (const char *[]){
+- "pcnoc_bfdcd_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_bfdcd_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -652,8 +318,9 @@ static struct clk_branch gcc_sleep_clk_s
+ .enable_mask = BIT(1),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sleep_clk_src",
+- .parent_names = (const char *[]){
+- "sleep_clk"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "sleep_clk",
++ .name = "sleep_clk",
+ },
+ .num_parents = 1,
+ .ops = &clk_branch2_ops,
+@@ -676,8 +343,8 @@ static struct clk_rcg2 blsp1_qup1_i2c_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup1_i2c_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -702,8 +369,8 @@ static struct clk_rcg2 blsp1_qup1_spi_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup1_spi_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -715,8 +382,8 @@ static struct clk_rcg2 blsp1_qup2_i2c_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup2_i2c_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -729,8 +396,8 @@ static struct clk_rcg2 blsp1_qup2_spi_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup2_spi_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -742,8 +409,8 @@ static struct clk_rcg2 blsp1_qup3_i2c_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup3_i2c_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -756,8 +423,8 @@ static struct clk_rcg2 blsp1_qup3_spi_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup3_spi_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -769,8 +436,8 @@ static struct clk_rcg2 blsp1_qup4_i2c_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup4_i2c_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -783,8 +450,8 @@ static struct clk_rcg2 blsp1_qup4_spi_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup4_spi_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -796,8 +463,8 @@ static struct clk_rcg2 blsp1_qup5_i2c_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup5_i2c_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -810,8 +477,8 @@ static struct clk_rcg2 blsp1_qup5_spi_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup5_spi_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -823,8 +490,8 @@ static struct clk_rcg2 blsp1_qup6_i2c_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup6_i2c_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -837,8 +504,8 @@ static struct clk_rcg2 blsp1_qup6_spi_ap
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_qup6_spi_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -871,8 +538,8 @@ static struct clk_rcg2 blsp1_uart1_apps_
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_uart1_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -885,8 +552,8 @@ static struct clk_rcg2 blsp1_uart2_apps_
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_uart2_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -899,8 +566,8 @@ static struct clk_rcg2 blsp1_uart3_apps_
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_uart3_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -913,8 +580,8 @@ static struct clk_rcg2 blsp1_uart4_apps_
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_uart4_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -927,8 +594,8 @@ static struct clk_rcg2 blsp1_uart5_apps_
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_uart5_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -941,8 +608,8 @@ static struct clk_rcg2 blsp1_uart6_apps_
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "blsp1_uart6_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -952,6 +619,11 @@ static const struct clk_parent_data gcc_
+ { .hw = &gpll0.clkr.hw },
+ };
+
++static const struct parent_map gcc_xo_gpll0_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++};
++
+ static const struct freq_tbl ftbl_pcie_axi_clk_src[] = {
+ F(19200000, P_XO, 1, 0, 0),
+ F(200000000, P_GPLL0, 4, 0, 0),
+@@ -966,7 +638,7 @@ static struct clk_rcg2 pcie0_axi_clk_src
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pcie0_axi_clk_src",
+ .parent_data = gcc_xo_gpll0,
+- .num_parents = 2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -975,6 +647,18 @@ static const struct freq_tbl ftbl_pcie_a
+ F(19200000, P_XO, 1, 0, 0),
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_sleep_clk[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw },
++ { .fw_name = "sleep_clk", .name = "sleep_clk" },
++};
++
++static const struct parent_map gcc_xo_gpll0_sleep_clk_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 2 },
++ { P_SLEEP_CLK, 6 },
++};
++
+ static struct clk_rcg2 pcie0_aux_clk_src = {
+ .cmd_rcgr = 0x75024,
+ .freq_tbl = ftbl_pcie_aux_clk_src,
+@@ -983,12 +667,22 @@ static struct clk_rcg2 pcie0_aux_clk_src
+ .parent_map = gcc_xo_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pcie0_aux_clk_src",
+- .parent_names = gcc_xo_gpll0_sleep_clk,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+
++static const struct clk_parent_data gcc_pcie20_phy0_pipe_clk_xo[] = {
++ { .name = "pcie20_phy0_pipe_clk" },
++ { .fw_name = "xo", .name = "xo" },
++};
++
++static const struct parent_map gcc_pcie20_phy0_pipe_clk_xo_map[] = {
++ { P_PCIE20_PHY0_PIPE, 0 },
++ { P_XO, 2 },
++};
++
+ static struct clk_regmap_mux pcie0_pipe_clk_src = {
+ .reg = 0x7501c,
+ .shift = 8,
+@@ -997,8 +691,8 @@ static struct clk_regmap_mux pcie0_pipe_
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "pcie0_pipe_clk_src",
+- .parent_names = gcc_pcie20_phy0_pipe_clk_xo,
+- .num_parents = 2,
++ .parent_data = gcc_pcie20_phy0_pipe_clk_xo,
++ .num_parents = ARRAY_SIZE(gcc_pcie20_phy0_pipe_clk_xo),
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+@@ -1013,7 +707,7 @@ static struct clk_rcg2 pcie1_axi_clk_src
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pcie1_axi_clk_src",
+ .parent_data = gcc_xo_gpll0,
+- .num_parents = 2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1026,12 +720,22 @@ static struct clk_rcg2 pcie1_aux_clk_src
+ .parent_map = gcc_xo_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pcie1_aux_clk_src",
+- .parent_names = gcc_xo_gpll0_sleep_clk,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+
++static const struct clk_parent_data gcc_pcie20_phy1_pipe_clk_xo[] = {
++ { .name = "pcie20_phy1_pipe_clk" },
++ { .fw_name = "xo", .name = "xo" },
++};
++
++static const struct parent_map gcc_pcie20_phy1_pipe_clk_xo_map[] = {
++ { P_PCIE20_PHY1_PIPE, 0 },
++ { P_XO, 2 },
++};
++
+ static struct clk_regmap_mux pcie1_pipe_clk_src = {
+ .reg = 0x7601c,
+ .shift = 8,
+@@ -1040,8 +744,8 @@ static struct clk_regmap_mux pcie1_pipe_
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "pcie1_pipe_clk_src",
+- .parent_names = gcc_pcie20_phy1_pipe_clk_xo,
+- .num_parents = 2,
++ .parent_data = gcc_pcie20_phy1_pipe_clk_xo,
++ .num_parents = ARRAY_SIZE(gcc_pcie20_phy1_pipe_clk_xo),
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+@@ -1060,6 +764,20 @@ static const struct freq_tbl ftbl_sdcc_a
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_gpll2_gpll0_out_main_div2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll2.clkr.hw },
++ { .hw = &gpll0_out_main_div2.hw },
++};
++
++static const struct parent_map gcc_xo_gpll0_gpll2_gpll0_out_main_div2_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++ { P_GPLL2, 2 },
++ { P_GPLL0_DIV2, 4 },
++};
++
+ static struct clk_rcg2 sdcc1_apps_clk_src = {
+ .cmd_rcgr = 0x42004,
+ .freq_tbl = ftbl_sdcc_apps_clk_src,
+@@ -1068,8 +786,8 @@ static struct clk_rcg2 sdcc1_apps_clk_sr
+ .parent_map = gcc_xo_gpll0_gpll2_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "sdcc1_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll2_gpll0_out_main_div2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_gpll0_gpll2_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll2_gpll0_out_main_div2),
+ .ops = &clk_rcg2_floor_ops,
+ },
+ };
+@@ -1080,6 +798,20 @@ static const struct freq_tbl ftbl_sdcc_i
+ F(308570000, P_GPLL6, 3.5, 0, 0),
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_gpll6_gpll0_div2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll6.clkr.hw },
++ { .hw = &gpll0_out_main_div2.hw },
++};
++
++static const struct parent_map gcc_xo_gpll0_gpll6_gpll0_div2_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++ { P_GPLL6, 2 },
++ { P_GPLL0_DIV2, 4 },
++};
++
+ static struct clk_rcg2 sdcc1_ice_core_clk_src = {
+ .cmd_rcgr = 0x5d000,
+ .freq_tbl = ftbl_sdcc_ice_core_clk_src,
+@@ -1088,8 +820,8 @@ static struct clk_rcg2 sdcc1_ice_core_cl
+ .parent_map = gcc_xo_gpll0_gpll6_gpll0_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "sdcc1_ice_core_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll6_gpll0_div2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_gpll0_gpll6_gpll0_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll6_gpll0_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1102,8 +834,8 @@ static struct clk_rcg2 sdcc2_apps_clk_sr
+ .parent_map = gcc_xo_gpll0_gpll2_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "sdcc2_apps_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll2_gpll0_out_main_div2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_gpll0_gpll2_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll2_gpll0_out_main_div2),
+ .ops = &clk_rcg2_floor_ops,
+ },
+ };
+@@ -1115,6 +847,18 @@ static const struct freq_tbl ftbl_usb_ma
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_out_main_div2_gpll0[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0_out_main_div2.hw },
++ { .hw = &gpll0.clkr.hw },
++};
++
++static const struct parent_map gcc_xo_gpll0_out_main_div2_gpll0_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0_DIV2, 2 },
++ { P_GPLL0, 1 },
++};
++
+ static struct clk_rcg2 usb0_master_clk_src = {
+ .cmd_rcgr = 0x3e00c,
+ .freq_tbl = ftbl_usb_master_clk_src,
+@@ -1123,8 +867,8 @@ static struct clk_rcg2 usb0_master_clk_s
+ .parent_map = gcc_xo_gpll0_out_main_div2_gpll0_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "usb0_master_clk_src",
+- .parent_names = gcc_xo_gpll0_out_main_div2_gpll0,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_out_main_div2_gpll0,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_out_main_div2_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1142,8 +886,8 @@ static struct clk_rcg2 usb0_aux_clk_src
+ .parent_map = gcc_xo_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "usb0_aux_clk_src",
+- .parent_names = gcc_xo_gpll0_sleep_clk,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1155,6 +899,20 @@ static const struct freq_tbl ftbl_usb_mo
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll6_gpll0_gpll0_out_main_div2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll6.clkr.hw },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll0_out_main_div2.hw },
++};
++
++static const struct parent_map gcc_xo_gpll6_gpll0_gpll0_out_main_div2_map[] = {
++ { P_XO, 0 },
++ { P_GPLL6, 1 },
++ { P_GPLL0, 3 },
++ { P_GPLL0_DIV2, 4 },
++};
++
+ static struct clk_rcg2 usb0_mock_utmi_clk_src = {
+ .cmd_rcgr = 0x3e020,
+ .freq_tbl = ftbl_usb_mock_utmi_clk_src,
+@@ -1163,12 +921,22 @@ static struct clk_rcg2 usb0_mock_utmi_cl
+ .parent_map = gcc_xo_gpll6_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "usb0_mock_utmi_clk_src",
+- .parent_names = gcc_xo_gpll6_gpll0_gpll0_out_main_div2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_gpll6_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll6_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+
++static const struct clk_parent_data gcc_usb3phy_0_cc_pipe_clk_xo[] = {
++ { .name = "usb3phy_0_cc_pipe_clk" },
++ { .fw_name = "xo", .name = "xo" },
++};
++
++static const struct parent_map gcc_usb3phy_0_cc_pipe_clk_xo_map[] = {
++ { P_USB3PHY_0_PIPE, 0 },
++ { P_XO, 2 },
++};
++
+ static struct clk_regmap_mux usb0_pipe_clk_src = {
+ .reg = 0x3e048,
+ .shift = 8,
+@@ -1177,8 +945,8 @@ static struct clk_regmap_mux usb0_pipe_c
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "usb0_pipe_clk_src",
+- .parent_names = gcc_usb3phy_0_cc_pipe_clk_xo,
+- .num_parents = 2,
++ .parent_data = gcc_usb3phy_0_cc_pipe_clk_xo,
++ .num_parents = ARRAY_SIZE(gcc_usb3phy_0_cc_pipe_clk_xo),
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+@@ -1193,8 +961,8 @@ static struct clk_rcg2 usb1_master_clk_s
+ .parent_map = gcc_xo_gpll0_out_main_div2_gpll0_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "usb1_master_clk_src",
+- .parent_names = gcc_xo_gpll0_out_main_div2_gpll0,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_out_main_div2_gpll0,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_out_main_div2_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1207,8 +975,8 @@ static struct clk_rcg2 usb1_aux_clk_src
+ .parent_map = gcc_xo_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "usb1_aux_clk_src",
+- .parent_names = gcc_xo_gpll0_sleep_clk,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1221,12 +989,22 @@ static struct clk_rcg2 usb1_mock_utmi_cl
+ .parent_map = gcc_xo_gpll6_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "usb1_mock_utmi_clk_src",
+- .parent_names = gcc_xo_gpll6_gpll0_gpll0_out_main_div2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_gpll6_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll6_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+
++static const struct clk_parent_data gcc_usb3phy_1_cc_pipe_clk_xo[] = {
++ { .name = "usb3phy_1_cc_pipe_clk" },
++ { .fw_name = "xo", .name = "xo" },
++};
++
++static const struct parent_map gcc_usb3phy_1_cc_pipe_clk_xo_map[] = {
++ { P_USB3PHY_1_PIPE, 0 },
++ { P_XO, 2 },
++};
++
+ static struct clk_regmap_mux usb1_pipe_clk_src = {
+ .reg = 0x3f048,
+ .shift = 8,
+@@ -1235,8 +1013,8 @@ static struct clk_regmap_mux usb1_pipe_c
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "usb1_pipe_clk_src",
+- .parent_names = gcc_usb3phy_1_cc_pipe_clk_xo,
+- .num_parents = 2,
++ .parent_data = gcc_usb3phy_1_cc_pipe_clk_xo,
++ .num_parents = ARRAY_SIZE(gcc_usb3phy_1_cc_pipe_clk_xo),
+ .ops = &clk_regmap_mux_closest_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+@@ -1250,8 +1028,9 @@ static struct clk_branch gcc_xo_clk_src
+ .enable_mask = BIT(1),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_xo_clk_src",
+- .parent_names = (const char *[]){
+- "xo"
++ .parent_data = &(const struct clk_parent_data){
++ .fw_name = "xo",
++ .name = "xo",
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
+@@ -1265,9 +1044,8 @@ static struct clk_fixed_factor gcc_xo_di
+ .div = 4,
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_xo_div4_clk_src",
+- .parent_names = (const char *[]){
+- "gcc_xo_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1285,6 +1063,20 @@ static const struct freq_tbl ftbl_system
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_gpll6_gpll0_out_main_div2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll6.clkr.hw },
++ { .hw = &gpll0_out_main_div2.hw },
++};
++
++static const struct parent_map gcc_xo_gpll0_gpll6_gpll0_out_main_div2_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++ { P_GPLL6, 2 },
++ { P_GPLL0_DIV2, 3 },
++};
++
+ static struct clk_rcg2 system_noc_bfdcd_clk_src = {
+ .cmd_rcgr = 0x26004,
+ .freq_tbl = ftbl_system_noc_bfdcd_clk_src,
+@@ -1292,8 +1084,8 @@ static struct clk_rcg2 system_noc_bfdcd_
+ .parent_map = gcc_xo_gpll0_gpll6_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "system_noc_bfdcd_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll6_gpll0_out_main_div2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_gpll0_gpll6_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll6_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ .flags = CLK_IS_CRITICAL,
+ },
+@@ -1304,9 +1096,8 @@ static struct clk_fixed_factor system_no
+ .div = 1,
+ .hw.init = &(struct clk_init_data){
+ .name = "system_noc_clk_src",
+- .parent_names = (const char *[]){
+- "system_noc_bfdcd_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &system_noc_bfdcd_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1327,7 +1118,7 @@ static struct clk_rcg2 nss_ce_clk_src =
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_ce_clk_src",
+ .parent_data = gcc_xo_gpll0,
+- .num_parents = 2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1338,6 +1129,20 @@ static const struct freq_tbl ftbl_nss_no
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "bias_pll_nss_noc_clk" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll2.clkr.hw },
++};
++
++static const struct parent_map gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2_map[] = {
++ { P_XO, 0 },
++ { P_BIAS_PLL_NSS_NOC, 1 },
++ { P_GPLL0, 2 },
++ { P_GPLL2, 3 },
++};
++
+ static struct clk_rcg2 nss_noc_bfdcd_clk_src = {
+ .cmd_rcgr = 0x68088,
+ .freq_tbl = ftbl_nss_noc_bfdcd_clk_src,
+@@ -1345,8 +1150,8 @@ static struct clk_rcg2 nss_noc_bfdcd_clk
+ .parent_map = gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_noc_bfdcd_clk_src",
+- .parent_names = gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2,
+- .num_parents = 4,
++ .parent_data = gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2,
++ .num_parents = ARRAY_SIZE(gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1356,9 +1161,8 @@ static struct clk_fixed_factor nss_noc_c
+ .div = 1,
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_noc_clk_src",
+- .parent_names = (const char *[]){
+- "nss_noc_bfdcd_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_bfdcd_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1371,6 +1175,18 @@ static const struct freq_tbl ftbl_nss_cr
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_nss_crypto_pll_gpll0[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &nss_crypto_pll.clkr.hw },
++ { .hw = &gpll0.clkr.hw },
++};
++
++static const struct parent_map gcc_xo_nss_crypto_pll_gpll0_map[] = {
++ { P_XO, 0 },
++ { P_NSS_CRYPTO_PLL, 1 },
++ { P_GPLL0, 2 },
++};
++
+ static struct clk_rcg2 nss_crypto_clk_src = {
+ .cmd_rcgr = 0x68144,
+ .freq_tbl = ftbl_nss_crypto_clk_src,
+@@ -1379,8 +1195,8 @@ static struct clk_rcg2 nss_crypto_clk_sr
+ .parent_map = gcc_xo_nss_crypto_pll_gpll0_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_crypto_clk_src",
+- .parent_names = gcc_xo_nss_crypto_pll_gpll0,
+- .num_parents = 3,
++ .parent_data = gcc_xo_nss_crypto_pll_gpll0,
++ .num_parents = ARRAY_SIZE(gcc_xo_nss_crypto_pll_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1394,6 +1210,24 @@ static const struct freq_tbl ftbl_nss_ub
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll2.clkr.hw },
++ { .hw = &gpll4.clkr.hw },
++ { .hw = &gpll6.clkr.hw },
++};
++
++static const struct parent_map gcc_xo_ubi32_gpll0_gpll2_gpll4_gpll6_map[] = {
++ { P_XO, 0 },
++ { P_UBI32_PLL, 1 },
++ { P_GPLL0, 2 },
++ { P_GPLL2, 3 },
++ { P_GPLL4, 4 },
++ { P_GPLL6, 5 },
++};
++
+ static struct clk_rcg2 nss_ubi0_clk_src = {
+ .cmd_rcgr = 0x68104,
+ .freq_tbl = ftbl_nss_ubi_clk_src,
+@@ -1401,8 +1235,8 @@ static struct clk_rcg2 nss_ubi0_clk_src
+ .parent_map = gcc_xo_ubi32_gpll0_gpll2_gpll4_gpll6_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_ubi0_clk_src",
+- .parent_names = gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6,
+- .num_parents = 6,
++ .parent_data = gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6,
++ .num_parents = ARRAY_SIZE(gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6),
+ .ops = &clk_rcg2_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+@@ -1415,9 +1249,8 @@ static struct clk_regmap_div nss_ubi0_di
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_ubi0_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_ubi0_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ubi0_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ro_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1432,8 +1265,8 @@ static struct clk_rcg2 nss_ubi1_clk_src
+ .parent_map = gcc_xo_ubi32_gpll0_gpll2_gpll4_gpll6_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_ubi1_clk_src",
+- .parent_names = gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6,
+- .num_parents = 6,
++ .parent_data = gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6,
++ .num_parents = ARRAY_SIZE(gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6),
+ .ops = &clk_rcg2_ops,
+ .flags = CLK_SET_RATE_PARENT,
+ },
+@@ -1446,9 +1279,8 @@ static struct clk_regmap_div nss_ubi1_di
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_ubi1_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_ubi1_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ubi1_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ro_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1462,6 +1294,16 @@ static const struct freq_tbl ftbl_ubi_mp
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_out_main_div2[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0_out_main_div2.hw },
++};
++
++static const struct parent_map gcc_xo_gpll0_out_main_div2_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0_DIV2, 1 },
++};
++
+ static struct clk_rcg2 ubi_mpt_clk_src = {
+ .cmd_rcgr = 0x68090,
+ .freq_tbl = ftbl_ubi_mpt_clk_src,
+@@ -1469,8 +1311,8 @@ static struct clk_rcg2 ubi_mpt_clk_src =
+ .parent_map = gcc_xo_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "ubi_mpt_clk_src",
+- .parent_names = gcc_xo_gpll0_out_main_div2,
+- .num_parents = 2,
++ .parent_data = gcc_xo_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1481,6 +1323,18 @@ static const struct freq_tbl ftbl_nss_im
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_gpll4[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll4.clkr.hw },
++};
++
++static const struct parent_map gcc_xo_gpll0_gpll4_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++ { P_GPLL4, 2 },
++};
++
+ static struct clk_rcg2 nss_imem_clk_src = {
+ .cmd_rcgr = 0x68158,
+ .freq_tbl = ftbl_nss_imem_clk_src,
+@@ -1488,8 +1342,8 @@ static struct clk_rcg2 nss_imem_clk_src
+ .parent_map = gcc_xo_gpll0_gpll4_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_imem_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll4,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll4,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll4),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1500,6 +1354,24 @@ static const struct freq_tbl ftbl_nss_pp
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_bias_gpll0_gpll4_nss_ubi32[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "bias_pll_cc_clk" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll4.clkr.hw },
++ { .hw = &nss_crypto_pll.clkr.hw },
++ { .hw = &ubi32_pll.clkr.hw },
++};
++
++static const struct parent_map gcc_xo_bias_gpll0_gpll4_nss_ubi32_map[] = {
++ { P_XO, 0 },
++ { P_BIAS_PLL, 1 },
++ { P_GPLL0, 2 },
++ { P_GPLL4, 3 },
++ { P_NSS_CRYPTO_PLL, 4 },
++ { P_UBI32_PLL, 5 },
++};
++
+ static struct clk_rcg2 nss_ppe_clk_src = {
+ .cmd_rcgr = 0x68080,
+ .freq_tbl = ftbl_nss_ppe_clk_src,
+@@ -1507,8 +1379,8 @@ static struct clk_rcg2 nss_ppe_clk_src =
+ .parent_map = gcc_xo_bias_gpll0_gpll4_nss_ubi32_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_ppe_clk_src",
+- .parent_names = gcc_xo_bias_gpll0_gpll4_nss_ubi32,
+- .num_parents = 6,
++ .parent_data = gcc_xo_bias_gpll0_gpll4_nss_ubi32,
++ .num_parents = ARRAY_SIZE(gcc_xo_bias_gpll0_gpll4_nss_ubi32),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1518,9 +1390,8 @@ static struct clk_fixed_factor nss_ppe_c
+ .div = 4,
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_ppe_cdiv_clk_src",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_fixed_factor_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1534,6 +1405,22 @@ static const struct freq_tbl ftbl_nss_po
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_uniphy0_rx_tx_ubi32_bias[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "uniphy0_gcc_rx_clk" },
++ { .name = "uniphy0_gcc_tx_clk" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .name = "bias_pll_cc_clk" },
++};
++
++static const struct parent_map gcc_xo_uniphy0_rx_tx_ubi32_bias_map[] = {
++ { P_XO, 0 },
++ { P_UNIPHY0_RX, 1 },
++ { P_UNIPHY0_TX, 2 },
++ { P_UBI32_PLL, 5 },
++ { P_BIAS_PLL, 6 },
++};
++
+ static struct clk_rcg2 nss_port1_rx_clk_src = {
+ .cmd_rcgr = 0x68020,
+ .freq_tbl = ftbl_nss_port1_rx_clk_src,
+@@ -1541,8 +1428,8 @@ static struct clk_rcg2 nss_port1_rx_clk_
+ .parent_map = gcc_xo_uniphy0_rx_tx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port1_rx_clk_src",
+- .parent_names = gcc_xo_uniphy0_rx_tx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_rx_tx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_rx_tx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1554,9 +1441,8 @@ static struct clk_regmap_div nss_port1_r
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port1_rx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port1_rx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port1_rx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1571,6 +1457,22 @@ static const struct freq_tbl ftbl_nss_po
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_uniphy0_tx_rx_ubi32_bias[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "uniphy0_gcc_tx_clk" },
++ { .name = "uniphy0_gcc_rx_clk" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .name = "bias_pll_cc_clk" },
++};
++
++static const struct parent_map gcc_xo_uniphy0_tx_rx_ubi32_bias_map[] = {
++ { P_XO, 0 },
++ { P_UNIPHY0_TX, 1 },
++ { P_UNIPHY0_RX, 2 },
++ { P_UBI32_PLL, 5 },
++ { P_BIAS_PLL, 6 },
++};
++
+ static struct clk_rcg2 nss_port1_tx_clk_src = {
+ .cmd_rcgr = 0x68028,
+ .freq_tbl = ftbl_nss_port1_tx_clk_src,
+@@ -1578,8 +1480,8 @@ static struct clk_rcg2 nss_port1_tx_clk_
+ .parent_map = gcc_xo_uniphy0_tx_rx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port1_tx_clk_src",
+- .parent_names = gcc_xo_uniphy0_tx_rx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_tx_rx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_tx_rx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1591,9 +1493,8 @@ static struct clk_regmap_div nss_port1_t
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port1_tx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port1_tx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port1_tx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1608,8 +1509,8 @@ static struct clk_rcg2 nss_port2_rx_clk_
+ .parent_map = gcc_xo_uniphy0_rx_tx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port2_rx_clk_src",
+- .parent_names = gcc_xo_uniphy0_rx_tx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_rx_tx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_rx_tx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1621,9 +1522,8 @@ static struct clk_regmap_div nss_port2_r
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port2_rx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port2_rx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port2_rx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1638,8 +1538,8 @@ static struct clk_rcg2 nss_port2_tx_clk_
+ .parent_map = gcc_xo_uniphy0_tx_rx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port2_tx_clk_src",
+- .parent_names = gcc_xo_uniphy0_tx_rx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_tx_rx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_tx_rx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1651,9 +1551,8 @@ static struct clk_regmap_div nss_port2_t
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port2_tx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port2_tx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port2_tx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1668,8 +1567,8 @@ static struct clk_rcg2 nss_port3_rx_clk_
+ .parent_map = gcc_xo_uniphy0_rx_tx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port3_rx_clk_src",
+- .parent_names = gcc_xo_uniphy0_rx_tx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_rx_tx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_rx_tx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1681,9 +1580,8 @@ static struct clk_regmap_div nss_port3_r
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port3_rx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port3_rx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port3_rx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1698,8 +1596,8 @@ static struct clk_rcg2 nss_port3_tx_clk_
+ .parent_map = gcc_xo_uniphy0_tx_rx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port3_tx_clk_src",
+- .parent_names = gcc_xo_uniphy0_tx_rx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_tx_rx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_tx_rx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1711,9 +1609,8 @@ static struct clk_regmap_div nss_port3_t
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port3_tx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port3_tx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port3_tx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1728,8 +1625,8 @@ static struct clk_rcg2 nss_port4_rx_clk_
+ .parent_map = gcc_xo_uniphy0_rx_tx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port4_rx_clk_src",
+- .parent_names = gcc_xo_uniphy0_rx_tx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_rx_tx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_rx_tx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1741,9 +1638,8 @@ static struct clk_regmap_div nss_port4_r
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port4_rx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port4_rx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port4_rx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1758,8 +1654,8 @@ static struct clk_rcg2 nss_port4_tx_clk_
+ .parent_map = gcc_xo_uniphy0_tx_rx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port4_tx_clk_src",
+- .parent_names = gcc_xo_uniphy0_tx_rx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy0_tx_rx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_tx_rx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1771,9 +1667,8 @@ static struct clk_regmap_div nss_port4_t
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port4_tx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port4_tx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port4_tx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1793,6 +1688,27 @@ static const struct freq_tbl ftbl_nss_po
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "uniphy0_gcc_rx_clk" },
++ { .name = "uniphy0_gcc_tx_clk" },
++ { .name = "uniphy1_gcc_rx_clk" },
++ { .name = "uniphy1_gcc_tx_clk" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .name = "bias_pll_cc_clk" },
++};
++
++static const struct parent_map
++gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias_map[] = {
++ { P_XO, 0 },
++ { P_UNIPHY0_RX, 1 },
++ { P_UNIPHY0_TX, 2 },
++ { P_UNIPHY1_RX, 3 },
++ { P_UNIPHY1_TX, 4 },
++ { P_UBI32_PLL, 5 },
++ { P_BIAS_PLL, 6 },
++};
++
+ static struct clk_rcg2 nss_port5_rx_clk_src = {
+ .cmd_rcgr = 0x68060,
+ .freq_tbl = ftbl_nss_port5_rx_clk_src,
+@@ -1800,8 +1716,8 @@ static struct clk_rcg2 nss_port5_rx_clk_
+ .parent_map = gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port5_rx_clk_src",
+- .parent_names = gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias,
+- .num_parents = 7,
++ .parent_data = gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1813,9 +1729,8 @@ static struct clk_regmap_div nss_port5_r
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port5_rx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port5_rx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_rx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1835,6 +1750,27 @@ static const struct freq_tbl ftbl_nss_po
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "uniphy0_gcc_tx_clk" },
++ { .name = "uniphy0_gcc_rx_clk" },
++ { .name = "uniphy1_gcc_tx_clk" },
++ { .name = "uniphy1_gcc_rx_clk" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .name = "bias_pll_cc_clk" },
++};
++
++static const struct parent_map
++gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias_map[] = {
++ { P_XO, 0 },
++ { P_UNIPHY0_TX, 1 },
++ { P_UNIPHY0_RX, 2 },
++ { P_UNIPHY1_TX, 3 },
++ { P_UNIPHY1_RX, 4 },
++ { P_UBI32_PLL, 5 },
++ { P_BIAS_PLL, 6 },
++};
++
+ static struct clk_rcg2 nss_port5_tx_clk_src = {
+ .cmd_rcgr = 0x68068,
+ .freq_tbl = ftbl_nss_port5_tx_clk_src,
+@@ -1842,8 +1778,8 @@ static struct clk_rcg2 nss_port5_tx_clk_
+ .parent_map = gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port5_tx_clk_src",
+- .parent_names = gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias,
+- .num_parents = 7,
++ .parent_data = gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1855,9 +1791,8 @@ static struct clk_regmap_div nss_port5_t
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port5_tx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port5_tx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_tx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1877,6 +1812,22 @@ static const struct freq_tbl ftbl_nss_po
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_uniphy2_rx_tx_ubi32_bias[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "uniphy2_gcc_rx_clk" },
++ { .name = "uniphy2_gcc_tx_clk" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .name = "bias_pll_cc_clk" },
++};
++
++static const struct parent_map gcc_xo_uniphy2_rx_tx_ubi32_bias_map[] = {
++ { P_XO, 0 },
++ { P_UNIPHY2_RX, 1 },
++ { P_UNIPHY2_TX, 2 },
++ { P_UBI32_PLL, 5 },
++ { P_BIAS_PLL, 6 },
++};
++
+ static struct clk_rcg2 nss_port6_rx_clk_src = {
+ .cmd_rcgr = 0x68070,
+ .freq_tbl = ftbl_nss_port6_rx_clk_src,
+@@ -1884,8 +1835,8 @@ static struct clk_rcg2 nss_port6_rx_clk_
+ .parent_map = gcc_xo_uniphy2_rx_tx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port6_rx_clk_src",
+- .parent_names = gcc_xo_uniphy2_rx_tx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy2_rx_tx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy2_rx_tx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1897,9 +1848,8 @@ static struct clk_regmap_div nss_port6_r
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port6_rx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port6_rx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port6_rx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1919,6 +1869,22 @@ static const struct freq_tbl ftbl_nss_po
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_uniphy2_tx_rx_ubi32_bias[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .name = "uniphy2_gcc_tx_clk" },
++ { .name = "uniphy2_gcc_rx_clk" },
++ { .hw = &ubi32_pll.clkr.hw },
++ { .name = "bias_pll_cc_clk" },
++};
++
++static const struct parent_map gcc_xo_uniphy2_tx_rx_ubi32_bias_map[] = {
++ { P_XO, 0 },
++ { P_UNIPHY2_TX, 1 },
++ { P_UNIPHY2_RX, 2 },
++ { P_UBI32_PLL, 5 },
++ { P_BIAS_PLL, 6 },
++};
++
+ static struct clk_rcg2 nss_port6_tx_clk_src = {
+ .cmd_rcgr = 0x68078,
+ .freq_tbl = ftbl_nss_port6_tx_clk_src,
+@@ -1926,8 +1892,8 @@ static struct clk_rcg2 nss_port6_tx_clk_
+ .parent_map = gcc_xo_uniphy2_tx_rx_ubi32_bias_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "nss_port6_tx_clk_src",
+- .parent_names = gcc_xo_uniphy2_tx_rx_ubi32_bias,
+- .num_parents = 5,
++ .parent_data = gcc_xo_uniphy2_tx_rx_ubi32_bias,
++ .num_parents = ARRAY_SIZE(gcc_xo_uniphy2_tx_rx_ubi32_bias),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1939,9 +1905,8 @@ static struct clk_regmap_div nss_port6_t
+ .clkr = {
+ .hw.init = &(struct clk_init_data){
+ .name = "nss_port6_tx_div_clk_src",
+- .parent_names = (const char *[]){
+- "nss_port6_tx_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port6_tx_clk_src.clkr.hw },
+ .num_parents = 1,
+ .ops = &clk_regmap_div_ops,
+ .flags = CLK_SET_RATE_PARENT,
+@@ -1964,8 +1929,8 @@ static struct clk_rcg2 crypto_clk_src =
+ .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "crypto_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll0_out_main_div2,
+- .num_parents = 3,
++ .parent_data = gcc_xo_gpll0_gpll0_out_main_div2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll0_out_main_div2),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1975,6 +1940,22 @@ static struct freq_tbl ftbl_gp_clk_src[]
+ { }
+ };
+
++static const struct clk_parent_data gcc_xo_gpll0_gpll6_gpll0_sleep_clk[] = {
++ { .fw_name = "xo", .name = "xo" },
++ { .hw = &gpll0.clkr.hw },
++ { .hw = &gpll6.clkr.hw },
++ { .hw = &gpll0_out_main_div2.hw },
++ { .fw_name = "sleep_clk", .name = "sleep_clk" },
++};
++
++static const struct parent_map gcc_xo_gpll0_gpll6_gpll0_sleep_clk_map[] = {
++ { P_XO, 0 },
++ { P_GPLL0, 1 },
++ { P_GPLL6, 2 },
++ { P_GPLL0_DIV2, 4 },
++ { P_SLEEP_CLK, 6 },
++};
++
+ static struct clk_rcg2 gp1_clk_src = {
+ .cmd_rcgr = 0x08004,
+ .freq_tbl = ftbl_gp_clk_src,
+@@ -1983,8 +1964,8 @@ static struct clk_rcg2 gp1_clk_src = {
+ .parent_map = gcc_xo_gpll0_gpll6_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gp1_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll6_gpll0_sleep_clk,
+- .num_parents = 5,
++ .parent_data = gcc_xo_gpll0_gpll6_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll6_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -1997,8 +1978,8 @@ static struct clk_rcg2 gp2_clk_src = {
+ .parent_map = gcc_xo_gpll0_gpll6_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gp2_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll6_gpll0_sleep_clk,
+- .num_parents = 5,
++ .parent_data = gcc_xo_gpll0_gpll6_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll6_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -2011,8 +1992,8 @@ static struct clk_rcg2 gp3_clk_src = {
+ .parent_map = gcc_xo_gpll0_gpll6_gpll0_sleep_clk_map,
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "gp3_clk_src",
+- .parent_names = gcc_xo_gpll0_gpll6_gpll0_sleep_clk,
+- .num_parents = 5,
++ .parent_data = gcc_xo_gpll0_gpll6_gpll0_sleep_clk,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0_gpll6_gpll0_sleep_clk),
+ .ops = &clk_rcg2_ops,
+ },
+ };
+@@ -2024,9 +2005,8 @@ static struct clk_branch gcc_blsp1_ahb_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2041,9 +2021,8 @@ static struct clk_branch gcc_blsp1_qup1_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup1_i2c_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup1_i2c_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup1_i2c_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2058,9 +2037,8 @@ static struct clk_branch gcc_blsp1_qup1_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup1_spi_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup1_spi_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup1_spi_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2075,9 +2053,8 @@ static struct clk_branch gcc_blsp1_qup2_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup2_i2c_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup2_i2c_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup2_i2c_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2092,9 +2069,8 @@ static struct clk_branch gcc_blsp1_qup2_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup2_spi_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup2_spi_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup2_spi_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2109,9 +2085,8 @@ static struct clk_branch gcc_blsp1_qup3_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup3_i2c_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup3_i2c_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup3_i2c_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2126,9 +2101,8 @@ static struct clk_branch gcc_blsp1_qup3_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup3_spi_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup3_spi_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup3_spi_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2143,9 +2117,8 @@ static struct clk_branch gcc_blsp1_qup4_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup4_i2c_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup4_i2c_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup4_i2c_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2160,9 +2133,8 @@ static struct clk_branch gcc_blsp1_qup4_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup4_spi_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup4_spi_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup4_spi_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2177,9 +2149,8 @@ static struct clk_branch gcc_blsp1_qup5_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup5_i2c_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup5_i2c_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup5_i2c_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2194,9 +2165,8 @@ static struct clk_branch gcc_blsp1_qup5_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup5_spi_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup5_spi_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup5_spi_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2211,9 +2181,8 @@ static struct clk_branch gcc_blsp1_qup6_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup6_i2c_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup6_i2c_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup6_i2c_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2228,9 +2197,8 @@ static struct clk_branch gcc_blsp1_qup6_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_qup6_spi_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_qup6_spi_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup6_spi_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2245,9 +2213,8 @@ static struct clk_branch gcc_blsp1_uart1
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_uart1_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_uart1_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_uart1_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2262,9 +2229,8 @@ static struct clk_branch gcc_blsp1_uart2
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_uart2_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_uart2_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_uart2_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2279,9 +2245,8 @@ static struct clk_branch gcc_blsp1_uart3
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_uart3_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_uart3_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_uart3_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2296,9 +2261,8 @@ static struct clk_branch gcc_blsp1_uart4
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_uart4_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_uart4_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_uart4_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2313,9 +2277,8 @@ static struct clk_branch gcc_blsp1_uart5
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_uart5_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_uart5_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_uart5_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2330,9 +2293,8 @@ static struct clk_branch gcc_blsp1_uart6
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_blsp1_uart6_apps_clk",
+- .parent_names = (const char *[]){
+- "blsp1_uart6_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_uart6_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2348,9 +2310,8 @@ static struct clk_branch gcc_prng_ahb_cl
+ .enable_mask = BIT(8),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_prng_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2365,9 +2326,8 @@ static struct clk_branch gcc_qpic_ahb_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_qpic_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2382,9 +2342,8 @@ static struct clk_branch gcc_qpic_clk =
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_qpic_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2399,9 +2358,8 @@ static struct clk_branch gcc_pcie0_ahb_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie0_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2416,9 +2374,8 @@ static struct clk_branch gcc_pcie0_aux_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie0_aux_clk",
+- .parent_names = (const char *[]){
+- "pcie0_aux_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie0_aux_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2433,9 +2390,8 @@ static struct clk_branch gcc_pcie0_axi_m
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie0_axi_m_clk",
+- .parent_names = (const char *[]){
+- "pcie0_axi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie0_axi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2450,9 +2406,8 @@ static struct clk_branch gcc_pcie0_axi_s
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie0_axi_s_clk",
+- .parent_names = (const char *[]){
+- "pcie0_axi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie0_axi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2468,9 +2423,8 @@ static struct clk_branch gcc_pcie0_pipe_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie0_pipe_clk",
+- .parent_names = (const char *[]){
+- "pcie0_pipe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie0_pipe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2485,9 +2439,8 @@ static struct clk_branch gcc_sys_noc_pci
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sys_noc_pcie0_axi_clk",
+- .parent_names = (const char *[]){
+- "pcie0_axi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie0_axi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2502,9 +2455,8 @@ static struct clk_branch gcc_pcie1_ahb_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie1_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2519,9 +2471,8 @@ static struct clk_branch gcc_pcie1_aux_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie1_aux_clk",
+- .parent_names = (const char *[]){
+- "pcie1_aux_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie1_aux_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2536,9 +2487,8 @@ static struct clk_branch gcc_pcie1_axi_m
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie1_axi_m_clk",
+- .parent_names = (const char *[]){
+- "pcie1_axi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie1_axi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2553,9 +2503,8 @@ static struct clk_branch gcc_pcie1_axi_s
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie1_axi_s_clk",
+- .parent_names = (const char *[]){
+- "pcie1_axi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie1_axi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2571,9 +2520,8 @@ static struct clk_branch gcc_pcie1_pipe_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_pcie1_pipe_clk",
+- .parent_names = (const char *[]){
+- "pcie1_pipe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie1_pipe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2588,9 +2536,8 @@ static struct clk_branch gcc_sys_noc_pci
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sys_noc_pcie1_axi_clk",
+- .parent_names = (const char *[]){
+- "pcie1_axi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcie1_axi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2605,9 +2552,8 @@ static struct clk_branch gcc_usb0_aux_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb0_aux_clk",
+- .parent_names = (const char *[]){
+- "usb0_aux_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb0_aux_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2622,9 +2568,8 @@ static struct clk_branch gcc_sys_noc_usb
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sys_noc_usb0_axi_clk",
+- .parent_names = (const char *[]){
+- "usb0_master_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb0_master_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2639,9 +2584,8 @@ static struct clk_branch gcc_usb0_master
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb0_master_clk",
+- .parent_names = (const char *[]){
+- "usb0_master_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb0_master_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2656,9 +2600,8 @@ static struct clk_branch gcc_usb0_mock_u
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb0_mock_utmi_clk",
+- .parent_names = (const char *[]){
+- "usb0_mock_utmi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb0_mock_utmi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2673,9 +2616,8 @@ static struct clk_branch gcc_usb0_phy_cf
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb0_phy_cfg_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2691,9 +2633,8 @@ static struct clk_branch gcc_usb0_pipe_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb0_pipe_clk",
+- .parent_names = (const char *[]){
+- "usb0_pipe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb0_pipe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2708,9 +2649,8 @@ static struct clk_branch gcc_usb0_sleep_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb0_sleep_clk",
+- .parent_names = (const char *[]){
+- "gcc_sleep_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_sleep_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2725,9 +2665,8 @@ static struct clk_branch gcc_usb1_aux_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb1_aux_clk",
+- .parent_names = (const char *[]){
+- "usb1_aux_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb1_aux_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2742,9 +2681,8 @@ static struct clk_branch gcc_sys_noc_usb
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sys_noc_usb1_axi_clk",
+- .parent_names = (const char *[]){
+- "usb1_master_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb1_master_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2759,9 +2697,8 @@ static struct clk_branch gcc_usb1_master
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb1_master_clk",
+- .parent_names = (const char *[]){
+- "usb1_master_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb1_master_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2776,9 +2713,8 @@ static struct clk_branch gcc_usb1_mock_u
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb1_mock_utmi_clk",
+- .parent_names = (const char *[]){
+- "usb1_mock_utmi_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb1_mock_utmi_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2793,9 +2729,8 @@ static struct clk_branch gcc_usb1_phy_cf
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb1_phy_cfg_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2811,9 +2746,8 @@ static struct clk_branch gcc_usb1_pipe_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb1_pipe_clk",
+- .parent_names = (const char *[]){
+- "usb1_pipe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &usb1_pipe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2828,9 +2762,8 @@ static struct clk_branch gcc_usb1_sleep_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_usb1_sleep_clk",
+- .parent_names = (const char *[]){
+- "gcc_sleep_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_sleep_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2845,9 +2778,8 @@ static struct clk_branch gcc_sdcc1_ahb_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sdcc1_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2862,9 +2794,8 @@ static struct clk_branch gcc_sdcc1_apps_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sdcc1_apps_clk",
+- .parent_names = (const char *[]){
+- "sdcc1_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &sdcc1_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2879,9 +2810,8 @@ static struct clk_branch gcc_sdcc1_ice_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sdcc1_ice_core_clk",
+- .parent_names = (const char *[]){
+- "sdcc1_ice_core_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &sdcc1_ice_core_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2896,9 +2826,8 @@ static struct clk_branch gcc_sdcc2_ahb_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sdcc2_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2913,9 +2842,8 @@ static struct clk_branch gcc_sdcc2_apps_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_sdcc2_apps_clk",
+- .parent_names = (const char *[]){
+- "sdcc2_apps_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &sdcc2_apps_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2930,9 +2858,8 @@ static struct clk_branch gcc_mem_noc_nss
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_mem_noc_nss_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2947,9 +2874,8 @@ static struct clk_branch gcc_nss_ce_apb_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ce_apb_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2964,9 +2890,8 @@ static struct clk_branch gcc_nss_ce_axi_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ce_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2981,9 +2906,8 @@ static struct clk_branch gcc_nss_cfg_clk
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_cfg_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -2998,9 +2922,8 @@ static struct clk_branch gcc_nss_crypto_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_crypto_clk",
+- .parent_names = (const char *[]){
+- "nss_crypto_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_crypto_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3015,9 +2938,8 @@ static struct clk_branch gcc_nss_csr_clk
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_csr_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3032,9 +2954,8 @@ static struct clk_branch gcc_nss_edma_cf
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_edma_cfg_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3049,9 +2970,8 @@ static struct clk_branch gcc_nss_edma_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_edma_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3066,9 +2986,8 @@ static struct clk_branch gcc_nss_imem_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_imem_clk",
+- .parent_names = (const char *[]){
+- "nss_imem_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_imem_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3083,9 +3002,8 @@ static struct clk_branch gcc_nss_noc_clk
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_noc_clk",
+- .parent_names = (const char *[]){
+- "nss_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3100,9 +3018,8 @@ static struct clk_branch gcc_nss_ppe_btq
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ppe_btq_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3117,9 +3034,8 @@ static struct clk_branch gcc_nss_ppe_cfg
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ppe_cfg_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3134,9 +3050,8 @@ static struct clk_branch gcc_nss_ppe_clk
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ppe_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3151,9 +3066,8 @@ static struct clk_branch gcc_nss_ppe_ipe
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ppe_ipe_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3168,9 +3082,8 @@ static struct clk_branch gcc_nss_ptp_ref
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_ptp_ref_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_cdiv_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_cdiv_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3186,9 +3099,8 @@ static struct clk_branch gcc_crypto_ppe_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_crypto_ppe_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3203,9 +3115,8 @@ static struct clk_branch gcc_nssnoc_ce_a
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_ce_apb_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3220,9 +3131,8 @@ static struct clk_branch gcc_nssnoc_ce_a
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_ce_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3237,9 +3147,8 @@ static struct clk_branch gcc_nssnoc_cryp
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_crypto_clk",
+- .parent_names = (const char *[]){
+- "nss_crypto_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_crypto_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3254,9 +3163,8 @@ static struct clk_branch gcc_nssnoc_ppe_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_ppe_cfg_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3271,9 +3179,8 @@ static struct clk_branch gcc_nssnoc_ppe_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_ppe_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3288,9 +3195,8 @@ static struct clk_branch gcc_nssnoc_qosg
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_qosgen_ref_clk",
+- .parent_names = (const char *[]){
+- "gcc_xo_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3305,9 +3211,8 @@ static struct clk_branch gcc_nssnoc_snoc
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_snoc_clk",
+- .parent_names = (const char *[]){
+- "system_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &system_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3322,9 +3227,8 @@ static struct clk_branch gcc_nssnoc_time
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_timeout_ref_clk",
+- .parent_names = (const char *[]){
+- "gcc_xo_div4_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_div4_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3339,9 +3243,8 @@ static struct clk_branch gcc_nssnoc_ubi0
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_ubi0_ahb_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3356,9 +3259,8 @@ static struct clk_branch gcc_nssnoc_ubi1
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nssnoc_ubi1_ahb_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3374,9 +3276,8 @@ static struct clk_branch gcc_ubi0_ahb_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi0_ahb_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3392,9 +3293,8 @@ static struct clk_branch gcc_ubi0_axi_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi0_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3410,9 +3310,8 @@ static struct clk_branch gcc_ubi0_nc_axi
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi0_nc_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3428,9 +3327,8 @@ static struct clk_branch gcc_ubi0_core_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi0_core_clk",
+- .parent_names = (const char *[]){
+- "nss_ubi0_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ubi0_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3446,9 +3344,8 @@ static struct clk_branch gcc_ubi0_mpt_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi0_mpt_clk",
+- .parent_names = (const char *[]){
+- "ubi_mpt_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &ubi_mpt_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3464,9 +3361,8 @@ static struct clk_branch gcc_ubi1_ahb_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi1_ahb_clk",
+- .parent_names = (const char *[]){
+- "nss_ce_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ce_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3482,9 +3378,8 @@ static struct clk_branch gcc_ubi1_axi_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi1_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3500,9 +3395,8 @@ static struct clk_branch gcc_ubi1_nc_axi
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi1_nc_axi_clk",
+- .parent_names = (const char *[]){
+- "nss_noc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_noc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3518,9 +3412,8 @@ static struct clk_branch gcc_ubi1_core_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi1_core_clk",
+- .parent_names = (const char *[]){
+- "nss_ubi1_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ubi1_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3536,9 +3429,8 @@ static struct clk_branch gcc_ubi1_mpt_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_ubi1_mpt_clk",
+- .parent_names = (const char *[]){
+- "ubi_mpt_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &ubi_mpt_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3553,9 +3445,8 @@ static struct clk_branch gcc_cmn_12gpll_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_cmn_12gpll_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3570,9 +3461,8 @@ static struct clk_branch gcc_cmn_12gpll_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_cmn_12gpll_sys_clk",
+- .parent_names = (const char *[]){
+- "gcc_xo_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3587,9 +3477,8 @@ static struct clk_branch gcc_mdio_ahb_cl
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_mdio_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3604,9 +3493,8 @@ static struct clk_branch gcc_uniphy0_ahb
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3621,9 +3509,8 @@ static struct clk_branch gcc_uniphy0_sys
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_sys_clk",
+- .parent_names = (const char *[]){
+- "gcc_xo_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3638,9 +3525,8 @@ static struct clk_branch gcc_uniphy1_ahb
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy1_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3655,9 +3541,8 @@ static struct clk_branch gcc_uniphy1_sys
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy1_sys_clk",
+- .parent_names = (const char *[]){
+- "gcc_xo_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3672,9 +3557,8 @@ static struct clk_branch gcc_uniphy2_ahb
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy2_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3689,9 +3573,8 @@ static struct clk_branch gcc_uniphy2_sys
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy2_sys_clk",
+- .parent_names = (const char *[]){
+- "gcc_xo_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gcc_xo_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3706,9 +3589,8 @@ static struct clk_branch gcc_nss_port1_r
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port1_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port1_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port1_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3723,9 +3605,8 @@ static struct clk_branch gcc_nss_port1_t
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port1_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port1_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port1_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3740,9 +3621,8 @@ static struct clk_branch gcc_nss_port2_r
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port2_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port2_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port2_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3757,9 +3637,8 @@ static struct clk_branch gcc_nss_port2_t
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port2_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port2_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port2_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3774,9 +3653,8 @@ static struct clk_branch gcc_nss_port3_r
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port3_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port3_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port3_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3791,9 +3669,8 @@ static struct clk_branch gcc_nss_port3_t
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port3_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port3_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port3_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3808,9 +3685,8 @@ static struct clk_branch gcc_nss_port4_r
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port4_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port4_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port4_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3825,9 +3701,8 @@ static struct clk_branch gcc_nss_port4_t
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port4_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port4_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port4_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3842,9 +3717,8 @@ static struct clk_branch gcc_nss_port5_r
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port5_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port5_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3859,9 +3733,8 @@ static struct clk_branch gcc_nss_port5_t
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port5_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port5_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3876,9 +3749,8 @@ static struct clk_branch gcc_nss_port6_r
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port6_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port6_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port6_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3893,9 +3765,8 @@ static struct clk_branch gcc_nss_port6_t
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_nss_port6_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port6_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port6_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3910,9 +3781,8 @@ static struct clk_branch gcc_port1_mac_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_port1_mac_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3927,9 +3797,8 @@ static struct clk_branch gcc_port2_mac_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_port2_mac_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3944,9 +3813,8 @@ static struct clk_branch gcc_port3_mac_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_port3_mac_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3961,9 +3829,8 @@ static struct clk_branch gcc_port4_mac_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_port4_mac_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3978,9 +3845,8 @@ static struct clk_branch gcc_port5_mac_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_port5_mac_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -3995,9 +3861,8 @@ static struct clk_branch gcc_port6_mac_c
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_port6_mac_clk",
+- .parent_names = (const char *[]){
+- "nss_ppe_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_ppe_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4012,9 +3877,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port1_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port1_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port1_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4029,9 +3893,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port1_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port1_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port1_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4046,9 +3909,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port2_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port2_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port2_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4063,9 +3925,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port2_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port2_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port2_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4080,9 +3941,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port3_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port3_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port3_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4097,9 +3957,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port3_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port3_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port3_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4114,9 +3973,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port4_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port4_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port4_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4131,9 +3989,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port4_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port4_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port4_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4148,9 +4005,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port5_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port5_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4165,9 +4021,8 @@ static struct clk_branch gcc_uniphy0_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy0_port5_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port5_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4182,9 +4037,8 @@ static struct clk_branch gcc_uniphy1_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy1_port5_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port5_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4199,9 +4053,8 @@ static struct clk_branch gcc_uniphy1_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy1_port5_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port5_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port5_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4216,9 +4069,8 @@ static struct clk_branch gcc_uniphy2_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy2_port6_rx_clk",
+- .parent_names = (const char *[]){
+- "nss_port6_rx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port6_rx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4233,9 +4085,8 @@ static struct clk_branch gcc_uniphy2_por
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_uniphy2_port6_tx_clk",
+- .parent_names = (const char *[]){
+- "nss_port6_tx_div_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &nss_port6_tx_div_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4251,9 +4102,8 @@ static struct clk_branch gcc_crypto_ahb_
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_crypto_ahb_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4269,9 +4119,8 @@ static struct clk_branch gcc_crypto_axi_
+ .enable_mask = BIT(1),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_crypto_axi_clk",
+- .parent_names = (const char *[]){
+- "pcnoc_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &pcnoc_clk_src.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4287,9 +4136,8 @@ static struct clk_branch gcc_crypto_clk
+ .enable_mask = BIT(2),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_crypto_clk",
+- .parent_names = (const char *[]){
+- "crypto_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &crypto_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4304,9 +4152,8 @@ static struct clk_branch gcc_gp1_clk = {
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_gp1_clk",
+- .parent_names = (const char *[]){
+- "gp1_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gp1_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4321,9 +4168,8 @@ static struct clk_branch gcc_gp2_clk = {
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_gp2_clk",
+- .parent_names = (const char *[]){
+- "gp2_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gp2_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4338,9 +4184,8 @@ static struct clk_branch gcc_gp3_clk = {
+ .enable_mask = BIT(0),
+ .hw.init = &(struct clk_init_data){
+ .name = "gcc_gp3_clk",
+- .parent_names = (const char *[]){
+- "gp3_clk_src"
+- },
++ .parent_hws = (const struct clk_hw *[]){
++ &gp3_clk_src.clkr.hw },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+@@ -4362,7 +4207,7 @@ static struct clk_rcg2 pcie0_rchng_clk_s
+ .clkr.hw.init = &(struct clk_init_data){
+ .name = "pcie0_rchng_clk_src",
+ .parent_data = gcc_xo_gpll0,
+- .num_parents = 2,
++ .num_parents = ARRAY_SIZE(gcc_xo_gpll0),
+ .ops = &clk_rcg2_ops,
+ },
+ };
diff --git a/target/linux/qualcommax/patches-6.1/0008-v6.2-arm64-dts-qcom-ipq6018-fix-NAND-node-name.patch b/target/linux/qualcommax/patches-6.1/0008-v6.2-arm64-dts-qcom-ipq6018-fix-NAND-node-name.patch
new file mode 100644
index 0000000000..a87ae4b8ad
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0008-v6.2-arm64-dts-qcom-ipq6018-fix-NAND-node-name.patch
@@ -0,0 +1,28 @@
+From 8857b0ab6a562c473c5bded0efda9390b82a84d4 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Tue, 27 Sep 2022 22:12:17 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: fix NAND node name
+
+Per schema it should be nand-controller@79b0000 instead of nand@79b0000.
+Fix it to match nand-controller.yaml requirements.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220927201218.1264506-1-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -348,7 +348,7 @@
+ status = "disabled";
+ };
+
+- qpic_nand: nand@79b0000 {
++ qpic_nand: nand-controller@79b0000 {
+ compatible = "qcom,ipq6018-nand";
+ reg = <0x0 0x079b0000 0x0 0x10000>;
+ #address-cells = <1>;
diff --git a/target/linux/qualcommax/patches-6.1/0009-v6.2-dt-bindings-clock-qcom-ipq8074-add-missing-networkin.patch b/target/linux/qualcommax/patches-6.1/0009-v6.2-dt-bindings-clock-qcom-ipq8074-add-missing-networkin.patch
new file mode 100644
index 0000000000..75f16a1673
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0009-v6.2-dt-bindings-clock-qcom-ipq8074-add-missing-networkin.patch
@@ -0,0 +1,39 @@
+From e78a40eb24187a8b4f9b89e2181f674df39c2013 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Mon, 7 Nov 2022 14:29:00 +0100
+Subject: [PATCH] dt-bindings: clock: qcom: ipq8074: add missing networking
+ resets
+
+Add bindings for the missing networking resets found in IPQ8074 GCC.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221107132901.489240-2-robimarko@gmail.com
+---
+ include/dt-bindings/clock/qcom,gcc-ipq8074.h | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h
++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h
+@@ -367,6 +367,20 @@
+ #define GCC_PCIE1_AHB_ARES 129
+ #define GCC_PCIE1_AXI_MASTER_STICKY_ARES 130
+ #define GCC_PCIE0_AXI_SLAVE_STICKY_ARES 131
++#define GCC_PPE_FULL_RESET 132
++#define GCC_UNIPHY0_SOFT_RESET 133
++#define GCC_UNIPHY0_XPCS_RESET 134
++#define GCC_UNIPHY1_SOFT_RESET 135
++#define GCC_UNIPHY1_XPCS_RESET 136
++#define GCC_UNIPHY2_SOFT_RESET 137
++#define GCC_UNIPHY2_XPCS_RESET 138
++#define GCC_EDMA_HW_RESET 139
++#define GCC_NSSPORT1_RESET 140
++#define GCC_NSSPORT2_RESET 141
++#define GCC_NSSPORT3_RESET 142
++#define GCC_NSSPORT4_RESET 143
++#define GCC_NSSPORT5_RESET 144
++#define GCC_NSSPORT6_RESET 145
+
+ #define USB0_GDSC 0
+ #define USB1_GDSC 1
diff --git a/target/linux/qualcommax/patches-6.1/0010-v6.2-clk-qcom-ipq8074-add-missing-networking-resets.patch b/target/linux/qualcommax/patches-6.1/0010-v6.2-clk-qcom-ipq8074-add-missing-networking-resets.patch
new file mode 100644
index 0000000000..81014ab24c
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0010-v6.2-clk-qcom-ipq8074-add-missing-networking-resets.patch
@@ -0,0 +1,41 @@
+From da76cb63d04dc22ed32123b8c1d084c006d67bfb Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Mon, 7 Nov 2022 14:29:01 +0100
+Subject: [PATCH] clk: qcom: ipq8074: add missing networking resets
+
+Downstream QCA 5.4 kernel defines networking resets which are not present
+in the mainline kernel but are required for the networking drivers.
+
+So, port the downstream resets and avoid using magic values for mask,
+construct mask for resets which require multiple bits to be set/cleared.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221107132901.489240-3-robimarko@gmail.com
+---
+ drivers/clk/qcom/gcc-ipq8074.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/clk/qcom/gcc-ipq8074.c
++++ b/drivers/clk/qcom/gcc-ipq8074.c
+@@ -4665,6 +4665,20 @@ static const struct qcom_reset_map gcc_i
+ [GCC_PCIE1_AXI_SLAVE_ARES] = { 0x76040, 4 },
+ [GCC_PCIE1_AHB_ARES] = { 0x76040, 5 },
+ [GCC_PCIE1_AXI_MASTER_STICKY_ARES] = { 0x76040, 6 },
++ [GCC_PPE_FULL_RESET] = { .reg = 0x68014, .bitmask = GENMASK(19, 16) },
++ [GCC_UNIPHY0_SOFT_RESET] = { .reg = 0x56004, .bitmask = GENMASK(13, 4) | BIT(1) },
++ [GCC_UNIPHY0_XPCS_RESET] = { 0x56004, 2 },
++ [GCC_UNIPHY1_SOFT_RESET] = { .reg = 0x56104, .bitmask = GENMASK(5, 4) | BIT(1) },
++ [GCC_UNIPHY1_XPCS_RESET] = { 0x56104, 2 },
++ [GCC_UNIPHY2_SOFT_RESET] = { .reg = 0x56204, .bitmask = GENMASK(5, 4) | BIT(1) },
++ [GCC_UNIPHY2_XPCS_RESET] = { 0x56204, 2 },
++ [GCC_EDMA_HW_RESET] = { .reg = 0x68014, .bitmask = GENMASK(21, 20) },
++ [GCC_NSSPORT1_RESET] = { .reg = 0x68014, .bitmask = BIT(24) | GENMASK(1, 0) },
++ [GCC_NSSPORT2_RESET] = { .reg = 0x68014, .bitmask = BIT(25) | GENMASK(3, 2) },
++ [GCC_NSSPORT3_RESET] = { .reg = 0x68014, .bitmask = BIT(26) | GENMASK(5, 4) },
++ [GCC_NSSPORT4_RESET] = { .reg = 0x68014, .bitmask = BIT(27) | GENMASK(9, 8) },
++ [GCC_NSSPORT5_RESET] = { .reg = 0x68014, .bitmask = BIT(28) | GENMASK(11, 10) },
++ [GCC_NSSPORT6_RESET] = { .reg = 0x68014, .bitmask = BIT(29) | GENMASK(13, 12) },
+ };
+
+ static struct gdsc *gcc_ipq8074_gdscs[] = {
diff --git a/target/linux/qualcommax/patches-6.1/0011-v6.2-clk-qcom-ipq8074-populate-fw_name-for-all-parents.patch b/target/linux/qualcommax/patches-6.1/0011-v6.2-clk-qcom-ipq8074-populate-fw_name-for-all-parents.patch
new file mode 100644
index 0000000000..35a0a07c70
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0011-v6.2-clk-qcom-ipq8074-populate-fw_name-for-all-parents.patch
@@ -0,0 +1,152 @@
+From 78936d46470938caa9a7ea529deeb36777b4f98e Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 16 Nov 2022 22:46:55 +0100
+Subject: [PATCH] clk: qcom: ipq8074: populate fw_name for all parents
+
+It appears that having only .name populated in parent_data for clocks
+which are only globally searchable currently will not work as the clk core
+won't copy that name if there is no .fw_name present as well.
+
+So, populate .fw_name for all parent clocks in parent_data.
+
+Fixes: ae55ad32e273 ("clk: qcom: ipq8074: convert to parent data")
+
+Co-developed-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221116214655.1116467-1-robimarko@gmail.com
+---
+ drivers/clk/qcom/gcc-ipq8074.c | 52 +++++++++++++++++-----------------
+ 1 file changed, 26 insertions(+), 26 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq8074.c
++++ b/drivers/clk/qcom/gcc-ipq8074.c
+@@ -674,7 +674,7 @@ static struct clk_rcg2 pcie0_aux_clk_src
+ };
+
+ static const struct clk_parent_data gcc_pcie20_phy0_pipe_clk_xo[] = {
+- { .name = "pcie20_phy0_pipe_clk" },
++ { .fw_name = "pcie0_pipe", .name = "pcie20_phy0_pipe_clk" },
+ { .fw_name = "xo", .name = "xo" },
+ };
+
+@@ -727,7 +727,7 @@ static struct clk_rcg2 pcie1_aux_clk_src
+ };
+
+ static const struct clk_parent_data gcc_pcie20_phy1_pipe_clk_xo[] = {
+- { .name = "pcie20_phy1_pipe_clk" },
++ { .fw_name = "pcie1_pipe", .name = "pcie20_phy1_pipe_clk" },
+ { .fw_name = "xo", .name = "xo" },
+ };
+
+@@ -1131,7 +1131,7 @@ static const struct freq_tbl ftbl_nss_no
+
+ static const struct clk_parent_data gcc_xo_bias_pll_nss_noc_clk_gpll0_gpll2[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "bias_pll_nss_noc_clk" },
++ { .fw_name = "bias_pll_nss_noc_clk", .name = "bias_pll_nss_noc_clk" },
+ { .hw = &gpll0.clkr.hw },
+ { .hw = &gpll2.clkr.hw },
+ };
+@@ -1356,7 +1356,7 @@ static const struct freq_tbl ftbl_nss_pp
+
+ static const struct clk_parent_data gcc_xo_bias_gpll0_gpll4_nss_ubi32[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ { .hw = &gpll0.clkr.hw },
+ { .hw = &gpll4.clkr.hw },
+ { .hw = &nss_crypto_pll.clkr.hw },
+@@ -1407,10 +1407,10 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy0_rx_tx_ubi32_bias[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "uniphy0_gcc_rx_clk" },
+- { .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_uniphy0_rx_tx_ubi32_bias_map[] = {
+@@ -1459,10 +1459,10 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy0_tx_rx_ubi32_bias[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "uniphy0_gcc_tx_clk" },
+- { .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_uniphy0_tx_rx_ubi32_bias_map[] = {
+@@ -1690,12 +1690,12 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "uniphy0_gcc_rx_clk" },
+- { .name = "uniphy0_gcc_tx_clk" },
+- { .name = "uniphy1_gcc_rx_clk" },
+- { .name = "uniphy1_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy1_gcc_rx_clk", .name = "uniphy1_gcc_rx_clk" },
++ { .fw_name = "uniphy1_gcc_tx_clk", .name = "uniphy1_gcc_tx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map
+@@ -1752,12 +1752,12 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "uniphy0_gcc_tx_clk" },
+- { .name = "uniphy0_gcc_rx_clk" },
+- { .name = "uniphy1_gcc_tx_clk" },
+- { .name = "uniphy1_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy1_gcc_tx_clk", .name = "uniphy1_gcc_tx_clk" },
++ { .fw_name = "uniphy1_gcc_rx_clk", .name = "uniphy1_gcc_rx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map
+@@ -1814,10 +1814,10 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy2_rx_tx_ubi32_bias[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "uniphy2_gcc_rx_clk" },
+- { .name = "uniphy2_gcc_tx_clk" },
++ { .fw_name = "uniphy2_gcc_rx_clk", .name = "uniphy2_gcc_rx_clk" },
++ { .fw_name = "uniphy2_gcc_tx_clk", .name = "uniphy2_gcc_tx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_uniphy2_rx_tx_ubi32_bias_map[] = {
+@@ -1871,10 +1871,10 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy2_tx_rx_ubi32_bias[] = {
+ { .fw_name = "xo", .name = "xo" },
+- { .name = "uniphy2_gcc_tx_clk" },
+- { .name = "uniphy2_gcc_rx_clk" },
++ { .fw_name = "uniphy2_gcc_tx_clk", .name = "uniphy2_gcc_tx_clk" },
++ { .fw_name = "uniphy2_gcc_rx_clk", .name = "uniphy2_gcc_rx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_uniphy2_tx_rx_ubi32_bias_map[] = {
diff --git a/target/linux/qualcommax/patches-6.1/0012-v6.2-arm64-dts-qcom-ipq8074-pass-XO-and-sleep-clocks-to-G.patch b/target/linux/qualcommax/patches-6.1/0012-v6.2-arm64-dts-qcom-ipq8074-pass-XO-and-sleep-clocks-to-G.patch
new file mode 100644
index 0000000000..0f3fdfe4d4
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0012-v6.2-arm64-dts-qcom-ipq8074-pass-XO-and-sleep-clocks-to-G.patch
@@ -0,0 +1,36 @@
+From 9033c3c86ea0dd35bd2ab957317573b755967298 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sun, 30 Oct 2022 18:57:03 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: pass XO and sleep clocks to GCC
+
+Pass XO and sleep clocks to the GCC controller so it does not have to
+find them by matching globaly by name.
+
+If not passed directly, driver maintains backwards compatibility by then
+falling back to global lookup.
+
+Since we are here, set cell numbers in decimal instead of hex.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221030175703.1103224-3-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -363,9 +363,11 @@
+ gcc: gcc@1800000 {
+ compatible = "qcom,gcc-ipq8074";
+ reg = <0x01800000 0x80000>;
+- #clock-cells = <0x1>;
++ clocks = <&xo>, <&sleep_clk>;
++ clock-names = "xo", "sleep_clk";
++ #clock-cells = <1>;
+ #power-domain-cells = <1>;
+- #reset-cells = <0x1>;
++ #reset-cells = <1>;
+ };
+
+ tcsr_mutex: hwlock@1905000 {
diff --git a/target/linux/qualcommax/patches-6.1/0013-v6.2-arm64-dts-qcom-add-PMP8074-DTSI.patch b/target/linux/qualcommax/patches-6.1/0013-v6.2-arm64-dts-qcom-add-PMP8074-DTSI.patch
new file mode 100644
index 0000000000..cd146420cf
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0013-v6.2-arm64-dts-qcom-add-PMP8074-DTSI.patch
@@ -0,0 +1,149 @@
+From fb76b808f8628215afebaf0f8af0bde635302590 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:18:14 +0200
+Subject: [PATCH] arm64: dts: qcom: add PMP8074 DTSI
+
+PMP8074 is a companion PMIC to the Qualcomm IPQ8074 series that is
+controlled via SPMI.
+
+Add DTSI for it providing GPIO, regulator, RTC and VADC support.
+
+RTC is disabled by default as there is no built-in battery so it will
+loose time unless board vendor added a battery, so make it optional.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220818221815.346233-4-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/pmp8074.dtsi | 125 ++++++++++++++++++++++++++
+ 1 file changed, 125 insertions(+)
+ create mode 100644 arch/arm64/boot/dts/qcom/pmp8074.dtsi
+
+--- /dev/null
++++ b/arch/arm64/boot/dts/qcom/pmp8074.dtsi
+@@ -0,0 +1,125 @@
++// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause
++
++#include <dt-bindings/spmi/spmi.h>
++#include <dt-bindings/iio/qcom,spmi-vadc.h>
++
++&spmi_bus {
++ pmic@0 {
++ compatible = "qcom,pmp8074", "qcom,spmi-pmic";
++ reg = <0x0 SPMI_USID>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ pmp8074_adc: adc@3100 {
++ compatible = "qcom,spmi-adc-rev2";
++ reg = <0x3100>;
++ interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>;
++ #address-cells = <1>;
++ #size-cells = <0>;
++ #io-channel-cells = <1>;
++
++ ref-gnd@0 {
++ reg = <ADC5_REF_GND>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ vref-1p25@1 {
++ reg = <ADC5_1P25VREF>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ vref-vadc@2 {
++ reg = <ADC5_VREF_VADC>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ pmic_die: die-temp@6 {
++ reg = <ADC5_DIE_TEMP>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ xo_therm: xo-temp@76 {
++ reg = <ADC5_XO_THERM_100K_PU>;
++ qcom,ratiometric;
++ qcom,hw-settle-time = <200>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ pa_therm1: thermistor1@77 {
++ reg = <ADC5_AMUX_THM1_100K_PU>;
++ qcom,ratiometric;
++ qcom,hw-settle-time = <200>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ pa_therm2: thermistor2@78 {
++ reg = <ADC5_AMUX_THM2_100K_PU>;
++ qcom,ratiometric;
++ qcom,hw-settle-time = <200>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ pa_therm3: thermistor3@79 {
++ reg = <ADC5_AMUX_THM3_100K_PU>;
++ qcom,ratiometric;
++ qcom,hw-settle-time = <200>;
++ qcom,pre-scaling = <1 1>;
++ };
++
++ vph-pwr@131 {
++ reg = <ADC5_VPH_PWR>;
++ qcom,pre-scaling = <1 3>;
++ };
++ };
++
++ pmp8074_rtc: rtc@6000 {
++ compatible = "qcom,pm8941-rtc";
++ reg = <0x6000>;
++ reg-names = "rtc", "alarm";
++ interrupts = <0x0 0x61 0x1 IRQ_TYPE_NONE>;
++ allow-set-time;
++ status = "disabled";
++ };
++
++ pmp8074_gpios: gpio@c000 {
++ compatible = "qcom,pmp8074-gpio", "qcom,spmi-gpio";
++ reg = <0xc000>;
++ gpio-controller;
++ #gpio-cells = <2>;
++ gpio-ranges = <&pmp8074_gpios 0 0 12>;
++ interrupt-controller;
++ #interrupt-cells = <2>;
++ };
++ };
++
++ pmic@1 {
++ compatible = "qcom,pmp8074", "qcom,spmi-pmic";
++ reg = <0x1 SPMI_USID>;
++
++ regulators {
++ compatible = "qcom,pmp8074-regulators";
++
++ s3: s3 {
++ regulator-name = "vdd_s3";
++ regulator-min-microvolt = <592000>;
++ regulator-max-microvolt = <1064000>;
++ regulator-always-on;
++ regulator-boot-on;
++ };
++
++ s4: s4 {
++ regulator-name = "vdd_s4";
++ regulator-min-microvolt = <712000>;
++ regulator-max-microvolt = <992000>;
++ regulator-always-on;
++ regulator-boot-on;
++ };
++
++ l11: l11 {
++ regulator-name = "l11";
++ regulator-min-microvolt = <1800000>;
++ regulator-max-microvolt = <3300000>;
++ };
++ };
++ };
++};
diff --git a/target/linux/qualcommax/patches-6.1/0014-v6.2-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch b/target/linux/qualcommax/patches-6.1/0014-v6.2-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch
new file mode 100644
index 0000000000..ebd3763a58
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0014-v6.2-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch
@@ -0,0 +1,37 @@
+From 2c394cfc1779886048feca7dc7f4075da5f6328c Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 19 Aug 2022 00:18:15 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074-hk01: add VQMMC supply
+
+Since now we have control over the PMP8074 PMIC providing various system
+voltages including L11 which provides the SDIO/eMMC I/O voltage set it as
+the SDHCI VQMMC supply.
+
+This allows SDHCI controller to switch to 1.8V I/O mode and support high
+speed modes like HS200 and HS400.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220818221815.346233-5-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074-hk01.dts | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
+@@ -3,6 +3,7 @@
+ /* Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+ #include "ipq8074.dtsi"
++#include "pmp8074.dtsi"
+
+ / {
+ model = "Qualcomm Technologies, Inc. IPQ8074-HK01";
+@@ -84,6 +85,7 @@
+
+ &sdhc_1 {
+ status = "okay";
++ vqmmc-supply = <&l11>;
+ };
+
+ &qusb_phy_0 {
diff --git a/target/linux/qualcommax/patches-6.1/0015-v6.2-arm64-dts-qcom-hk01-use-GPIO-flags-for-tlmm.patch b/target/linux/qualcommax/patches-6.1/0015-v6.2-arm64-dts-qcom-hk01-use-GPIO-flags-for-tlmm.patch
new file mode 100644
index 0000000000..e08f6d1f3c
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0015-v6.2-arm64-dts-qcom-hk01-use-GPIO-flags-for-tlmm.patch
@@ -0,0 +1,42 @@
+From 82ceb86227b1fc15c76d5fc691b2bf425f1a63b3 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Mon, 7 Nov 2022 10:29:30 +0100
+Subject: [PATCH] arm64: dts: qcom: hk01: use GPIO flags for tlmm
+
+Use respective GPIO_ACTIVE_LOW/HIGH flags for tlmm GPIOs instead of
+harcoding the cell value.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221107092930.33325-3-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074-hk01.dts | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
+@@ -4,6 +4,7 @@
+ */
+ #include "ipq8074.dtsi"
+ #include "pmp8074.dtsi"
++#include <dt-bindings/gpio/gpio.h>
+
+ / {
+ model = "Qualcomm Technologies, Inc. IPQ8074-HK01";
+@@ -52,12 +53,12 @@
+
+ &pcie0 {
+ status = "okay";
+- perst-gpios = <&tlmm 61 0x1>;
++ perst-gpios = <&tlmm 61 GPIO_ACTIVE_LOW>;
+ };
+
+ &pcie1 {
+ status = "okay";
+- perst-gpios = <&tlmm 58 0x1>;
++ perst-gpios = <&tlmm 58 GPIO_ACTIVE_LOW>;
+ };
+
+ &pcie_qmp0 {
diff --git a/target/linux/qualcommax/patches-6.1/0016-v6.2-arm64-dts-qcom-ipq8074-Fix-up-comments.patch b/target/linux/qualcommax/patches-6.1/0016-v6.2-arm64-dts-qcom-ipq8074-Fix-up-comments.patch
new file mode 100644
index 0000000000..a8bf2492f4
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0016-v6.2-arm64-dts-qcom-ipq8074-Fix-up-comments.patch
@@ -0,0 +1,82 @@
+From 1b1c1423ca3e740984aa883512a72c4ea08fbe28 Mon Sep 17 00:00:00 2001
+From: Konrad Dybcio <konrad.dybcio@linaro.org>
+Date: Mon, 7 Nov 2022 15:55:17 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074-*: Fix up comments
+
+Make sure all multiline C-style commends begin with just '/*' with
+the comment text starting on a new line.
+
+Also, fix up some whitespace within comments.
+
+Signed-off-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221107145522.6706-8-konrad.dybcio@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq8074-hk01.dts | 3 ++-
+ arch/arm64/boot/dts/qcom/ipq8074-hk10-c1.dts | 3 ++-
+ arch/arm64/boot/dts/qcom/ipq8074-hk10-c2.dts | 3 ++-
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 12 ++++++------
+ 4 files changed, 12 insertions(+), 9 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts
+@@ -1,6 +1,7 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+ /dts-v1/;
+-/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
++/*
++ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ */
+ #include "ipq8074.dtsi"
+ #include "pmp8074.dtsi"
+--- a/arch/arm64/boot/dts/qcom/ipq8074-hk10-c1.dts
++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk10-c1.dts
+@@ -1,5 +1,6 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+-/* Copyright (c) 2020 The Linux Foundation. All rights reserved.
++/*
++ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+ /dts-v1/;
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074-hk10-c2.dts
++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk10-c2.dts
+@@ -1,6 +1,7 @@
+ // SPDX-License-Identifier: GPL-2.0-only
+ /dts-v1/;
+-/* Copyright (c) 2020 The Linux Foundation. All rights reserved.
++/*
++ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+ #include "ipq8074-hk10.dtsi"
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -129,10 +129,10 @@
+ status = "disabled";
+
+ usb1_ssphy: phy@58200 {
+- reg = <0x00058200 0x130>, /* Tx */
++ reg = <0x00058200 0x130>, /* Tx */
+ <0x00058400 0x200>, /* Rx */
+- <0x00058800 0x1f8>, /* PCS */
+- <0x00058600 0x044>; /* PCS misc*/
++ <0x00058800 0x1f8>, /* PCS */
++ <0x00058600 0x044>; /* PCS misc */
+ #phy-cells = <0>;
+ #clock-cells = <0>;
+ clocks = <&gcc GCC_USB1_PIPE_CLK>;
+@@ -172,10 +172,10 @@
+ status = "disabled";
+
+ usb0_ssphy: phy@78200 {
+- reg = <0x00078200 0x130>, /* Tx */
++ reg = <0x00078200 0x130>, /* Tx */
+ <0x00078400 0x200>, /* Rx */
+- <0x00078800 0x1f8>, /* PCS */
+- <0x00078600 0x044>; /* PCS misc*/
++ <0x00078800 0x1f8>, /* PCS */
++ <0x00078600 0x044>; /* PCS misc */
+ #phy-cells = <0>;
+ #clock-cells = <0>;
+ clocks = <&gcc GCC_USB0_PIPE_CLK>;
diff --git a/target/linux/qualcommax/patches-6.1/0017-v6.2-arm64-dts-qcom-ipq8074-align-TLMM-pin-configuration-.patch b/target/linux/qualcommax/patches-6.1/0017-v6.2-arm64-dts-qcom-ipq8074-align-TLMM-pin-configuration-.patch
new file mode 100644
index 0000000000..c9fef2cab4
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0017-v6.2-arm64-dts-qcom-ipq8074-align-TLMM-pin-configuration-.patch
@@ -0,0 +1,60 @@
+From 5f20690f77878b1ba24ec88df01b92d5131a6780 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Tue, 8 Nov 2022 15:23:57 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: align TLMM pin configuration with
+ DT schema
+
+DT schema expects TLMM pin configuration nodes to be named with
+'-state' suffix and their optional children with '-pins' suffix.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221108142357.67202-2-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -320,35 +320,35 @@
+ interrupt-controller;
+ #interrupt-cells = <0x2>;
+
+- serial_4_pins: serial4-pinmux {
++ serial_4_pins: serial4-state {
+ pins = "gpio23", "gpio24";
+ function = "blsp4_uart1";
+ drive-strength = <8>;
+ bias-disable;
+ };
+
+- i2c_0_pins: i2c-0-pinmux {
++ i2c_0_pins: i2c-0-state {
+ pins = "gpio42", "gpio43";
+ function = "blsp1_i2c";
+ drive-strength = <8>;
+ bias-disable;
+ };
+
+- spi_0_pins: spi-0-pins {
++ spi_0_pins: spi-0-state {
+ pins = "gpio38", "gpio39", "gpio40", "gpio41";
+ function = "blsp0_spi";
+ drive-strength = <8>;
+ bias-disable;
+ };
+
+- hsuart_pins: hsuart-pins {
++ hsuart_pins: hsuart-state {
+ pins = "gpio46", "gpio47", "gpio48", "gpio49";
+ function = "blsp2_uart";
+ drive-strength = <8>;
+ bias-disable;
+ };
+
+- qpic_pins: qpic-pins {
++ qpic_pins: qpic-state {
+ pins = "gpio1", "gpio3", "gpio4",
+ "gpio5", "gpio6", "gpio7",
+ "gpio8", "gpio10", "gpio11",
diff --git a/target/linux/qualcommax/patches-6.1/0018-v6.2-arm64-dts-qcom-ipq6018-align-TLMM-pin-configuration-.patch b/target/linux/qualcommax/patches-6.1/0018-v6.2-arm64-dts-qcom-ipq6018-align-TLMM-pin-configuration-.patch
new file mode 100644
index 0000000000..ceaf68bf7a
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0018-v6.2-arm64-dts-qcom-ipq6018-align-TLMM-pin-configuration-.patch
@@ -0,0 +1,56 @@
+From 20afb6751739264ea41993877de93923911dfdc3 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Thu, 6 Oct 2022 14:46:27 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: align TLMM pin configuration with
+ DT schema
+
+DT schema expects TLMM pin configuration nodes to be named with
+'-state' suffix and their optional children with '-pins' suffix.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Bjorn Andersson <andersson@kernel.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@somainline.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20221006124659.217540-3-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dts | 4 ++--
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 4 ++--
+ 2 files changed, 4 insertions(+), 4 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dts
++++ b/arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dts
+@@ -51,13 +51,13 @@
+ };
+
+ &tlmm {
+- i2c_1_pins: i2c-1-pins {
++ i2c_1_pins: i2c-1-state {
+ pins = "gpio42", "gpio43";
+ function = "blsp2_i2c";
+ drive-strength = <8>;
+ };
+
+- spi_0_pins: spi-0-pins {
++ spi_0_pins: spi-0-state {
+ pins = "gpio38", "gpio39", "gpio40", "gpio41";
+ function = "blsp0_spi";
+ drive-strength = <8>;
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -218,14 +218,14 @@
+ interrupt-controller;
+ #interrupt-cells = <2>;
+
+- serial_3_pins: serial3-pinmux {
++ serial_3_pins: serial3-state {
+ pins = "gpio44", "gpio45";
+ function = "blsp2_uart";
+ drive-strength = <8>;
+ bias-pull-down;
+ };
+
+- qpic_pins: qpic-pins {
++ qpic_pins: qpic-state {
+ pins = "gpio1", "gpio3", "gpio4",
+ "gpio5", "gpio6", "gpio7",
+ "gpio8", "gpio10", "gpio11",
diff --git a/target/linux/qualcommax/patches-6.1/0019-v6.3-arm64-dts-qcom-ipq8074-set-Gen2-PCIe-pcie-max-link-s.patch b/target/linux/qualcommax/patches-6.1/0019-v6.3-arm64-dts-qcom-ipq8074-set-Gen2-PCIe-pcie-max-link-s.patch
new file mode 100644
index 0000000000..2578aa9343
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0019-v6.3-arm64-dts-qcom-ipq8074-set-Gen2-PCIe-pcie-max-link-s.patch
@@ -0,0 +1,24 @@
+From a4748d2850783d36f77ccf2b5fcc86ccf1800ef1 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 16 Nov 2022 22:48:36 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: set Gen2 PCIe pcie max-link-speed
+
+Add the generic 'max-link-speed' property to describe the Gen2 PCIe link
+generation limit.
+This allows the generic DWC code to configure the link speed correctly.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -768,6 +768,7 @@
+ linux,pci-domain = <1>;
+ bus-range = <0x00 0xff>;
+ num-lanes = <1>;
++ max-link-speed = <2>;
+ #address-cells = <3>;
+ #size-cells = <2>;
+
diff --git a/target/linux/qualcommax/patches-6.1/0020-v6.3-PCI-qcom-Add-support-for-IPQ8074-Gen3-port.patch b/target/linux/qualcommax/patches-6.1/0020-v6.3-PCI-qcom-Add-support-for-IPQ8074-Gen3-port.patch
new file mode 100644
index 0000000000..3d5c2182e9
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0020-v6.3-PCI-qcom-Add-support-for-IPQ8074-Gen3-port.patch
@@ -0,0 +1,26 @@
+From f356132229b18ceef5d5ef9103bbaa9bdeb84c8d Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 13 Jan 2023 17:44:47 +0100
+Subject: [PATCH] PCI: qcom: Add IPQ8074 Gen3 port support
+
+IPQ8074 has one Gen2 and one Gen3 port, with Gen2 port already supported.
+Add compatible for Gen3 port which uses the same controller as IPQ6018.
+
+Link: https://lore.kernel.org/r/20230113164449.906002-7-robimarko@gmail.com
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
+Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
+---
+ drivers/pci/controller/dwc/pcie-qcom.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/pci/controller/dwc/pcie-qcom.c
++++ b/drivers/pci/controller/dwc/pcie-qcom.c
+@@ -1762,6 +1762,7 @@ static const struct of_device_id qcom_pc
+ { .compatible = "qcom,pcie-ipq8064", .data = &cfg_2_1_0 },
+ { .compatible = "qcom,pcie-ipq8064-v2", .data = &cfg_2_1_0 },
+ { .compatible = "qcom,pcie-ipq8074", .data = &cfg_2_3_3 },
++ { .compatible = "qcom,pcie-ipq8074-gen3", .data = &cfg_2_9_0 },
+ { .compatible = "qcom,pcie-msm8996", .data = &cfg_2_3_2 },
+ { .compatible = "qcom,pcie-qcs404", .data = &cfg_2_4_0 },
+ { .compatible = "qcom,pcie-sa8540p", .data = &cfg_1_9_0 },
diff --git a/target/linux/qualcommax/patches-6.1/0021-v6.3-clk-qcom-ipq8074-populate-fw_name-for-usb3phy-s.patch b/target/linux/qualcommax/patches-6.1/0021-v6.3-clk-qcom-ipq8074-populate-fw_name-for-usb3phy-s.patch
new file mode 100644
index 0000000000..e0e8125ba6
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0021-v6.3-clk-qcom-ipq8074-populate-fw_name-for-usb3phy-s.patch
@@ -0,0 +1,38 @@
+From 614d31c231c7707322b643f409eeb7e28adc7f8c Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sun, 8 Jan 2023 13:36:28 +0100
+Subject: [PATCH] clk: qcom: ipq8074: populate fw_name for usb3phy-s
+
+Having only .name populated in parent_data for clocks which are only
+globally searchable currently will not work as the clk core won't copy
+that name if there is no .fw_name present as well.
+
+So, populate .fw_name for usb3phy clocks in parent_data as they were
+missed by me in ("clk: qcom: ipq8074: populate fw_name for all parents").
+
+Fixes: ae55ad32e273 ("clk: qcom: ipq8074: convert to parent data")
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/clk/qcom/gcc-ipq8074.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq8074.c
++++ b/drivers/clk/qcom/gcc-ipq8074.c
+@@ -928,7 +928,7 @@ static struct clk_rcg2 usb0_mock_utmi_cl
+ };
+
+ static const struct clk_parent_data gcc_usb3phy_0_cc_pipe_clk_xo[] = {
+- { .name = "usb3phy_0_cc_pipe_clk" },
++ { .fw_name = "usb3phy_0_cc_pipe_clk", .name = "usb3phy_0_cc_pipe_clk" },
+ { .fw_name = "xo", .name = "xo" },
+ };
+
+@@ -996,7 +996,7 @@ static struct clk_rcg2 usb1_mock_utmi_cl
+ };
+
+ static const struct clk_parent_data gcc_usb3phy_1_cc_pipe_clk_xo[] = {
+- { .name = "usb3phy_1_cc_pipe_clk" },
++ { .fw_name = "usb3phy_1_cc_pipe_clk", .name = "usb3phy_1_cc_pipe_clk" },
+ { .fw_name = "xo", .name = "xo" },
+ };
+
diff --git a/target/linux/qualcommax/patches-6.1/0022-v6.4-arm64-dts-qcom-ipq8074-add-compatible-fallback-to.patch b/target/linux/qualcommax/patches-6.1/0022-v6.4-arm64-dts-qcom-ipq8074-add-compatible-fallback-to.patch
new file mode 100644
index 0000000000..f85be793cd
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0022-v6.4-arm64-dts-qcom-ipq8074-add-compatible-fallback-to.patch
@@ -0,0 +1,26 @@
+From d93bd4630ce163f3761aedc0b342b072bee6db6b Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Wed, 22 Mar 2023 18:41:40 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add compatible fallback to mailbox
+
+IPQ8074 mailbox is compatible with IPQ6018.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230322174148.810938-4-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -682,7 +682,8 @@
+ };
+
+ apcs_glb: mailbox@b111000 {
+- compatible = "qcom,ipq8074-apcs-apps-global";
++ compatible = "qcom,ipq8074-apcs-apps-global",
++ "qcom,ipq6018-apcs-apps-global";
+ reg = <0x0b111000 0x1000>;
+ clocks = <&a53pll>, <&xo>;
+ clock-names = "pll", "xo";
diff --git a/target/linux/qualcommax/patches-6.1/0023-v6.5-arm64-dts-qcom-ipq8074-add-critical-thermal-trips.patch b/target/linux/qualcommax/patches-6.1/0023-v6.5-arm64-dts-qcom-ipq8074-add-critical-thermal-trips.patch
new file mode 100644
index 0000000000..737bb06752
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0023-v6.5-arm64-dts-qcom-ipq8074-add-critical-thermal-trips.patch
@@ -0,0 +1,199 @@
+From 56d3067cb694ba60d654e7f5ef231b6fabc4697f Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 7 Jun 2023 20:44:48 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add critical thermal trips
+
+According to bindings, thermal zones must have associated trips as well.
+Since we currently dont have CPUFreq support and thus no passive cooling
+lets start by defining critical trips to protect the devices against
+severe overheating.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230607184448.2512179-1-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 96 +++++++++++++++++++++++++++
+ 1 file changed, 96 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -899,6 +899,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 4>;
++
++ trips {
++ nss-top-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ nss0-thermal {
+@@ -906,6 +914,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 5>;
++
++ trips {
++ nss-0-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ nss1-thermal {
+@@ -913,6 +929,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 6>;
++
++ trips {
++ nss-1-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ wcss-phya0-thermal {
+@@ -920,6 +944,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 7>;
++
++ trips {
++ wcss-phya0-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ wcss-phya1-thermal {
+@@ -927,6 +959,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 8>;
++
++ trips {
++ wcss-phya1-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ cpu0_thermal: cpu0-thermal {
+@@ -934,6 +974,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 9>;
++
++ trips {
++ cpu0-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ cpu1_thermal: cpu1-thermal {
+@@ -941,6 +989,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 10>;
++
++ trips {
++ cpu1-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ cpu2_thermal: cpu2-thermal {
+@@ -948,6 +1004,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 11>;
++
++ trips {
++ cpu2-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ cpu3_thermal: cpu3-thermal {
+@@ -955,6 +1019,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 12>;
++
++ trips {
++ cpu3-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ cluster_thermal: cluster-thermal {
+@@ -962,6 +1034,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 13>;
++
++ trips {
++ cluster-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ wcss-phyb0-thermal {
+@@ -969,6 +1049,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 14>;
++
++ trips {
++ wcss-phyb0-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+
+ wcss-phyb1-thermal {
+@@ -976,6 +1064,14 @@
+ polling-delay = <1000>;
+
+ thermal-sensors = <&tsens 15>;
++
++ trips {
++ wcss-phyb1-crit {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
+ };
+ };
+ };
diff --git a/target/linux/qualcommax/patches-6.1/0024-v6.7-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ8174-family.patch b/target/linux/qualcommax/patches-6.1/0024-v6.7-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ8174-family.patch
new file mode 100644
index 0000000000..4eb0f097cc
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0024-v6.7-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ8174-family.patch
@@ -0,0 +1,29 @@
+From 93e161c8f4b9b051e5e746814138cb5520b4b897 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 1 Sep 2023 20:10:04 +0200
+Subject: [PATCH] dt-bindings: arm: qcom,ids: Add IDs for IPQ8174 family
+
+IPQ8174 (Oak) family is part of the IPQ8074 family, but the ID-s for it
+are missing so lets add them.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Kathiravan T <quic_kathirav@quicinc.com>
+Acked-by: Conor Dooley <conor.dooley@microchip.com>
+Link: https://lore.kernel.org/r/20230901181041.1538999-1-robimarko@gmail.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ include/dt-bindings/arm/qcom,ids.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/include/dt-bindings/arm/qcom,ids.h
++++ b/include/dt-bindings/arm/qcom,ids.h
+@@ -121,6 +121,9 @@
+ #define QCOM_ID_SM6125 394
+ #define QCOM_ID_IPQ8070A 395
+ #define QCOM_ID_IPQ8071A 396
++#define QCOM_ID_IPQ8172 397
++#define QCOM_ID_IPQ8173 398
++#define QCOM_ID_IPQ8174 399
+ #define QCOM_ID_IPQ6018 402
+ #define QCOM_ID_IPQ6028 403
+ #define QCOM_ID_IPQ6000 421
diff --git a/target/linux/qualcommax/patches-6.1/0025-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ6018.patch b/target/linux/qualcommax/patches-6.1/0025-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ6018.patch
new file mode 100644
index 0000000000..63e2f22db7
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0025-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ6018.patch
@@ -0,0 +1,123 @@
+From 47e161a7873b0891f4e01a69a839f6161d816ea8 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 25 Oct 2023 14:57:57 +0530
+Subject: [PATCH] cpufreq: qcom-nvmem: add support for IPQ6018
+
+IPQ6018 SoC series comes in multiple SKU-s, and not all of them support
+high frequency OPP points.
+
+SoC itself does however have a single bit in QFPROM to indicate the CPU
+speed-bin.
+That bit is used to indicate frequency limit of 1.5GHz, but that alone is
+not enough as IPQ6000 only goes up to 1.2GHz, but SMEM ID can be used to
+limit it further.
+
+IPQ6018 compatible is blacklisted from DT platdev as the cpufreq device
+will get created by NVMEM CPUFreq driver.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+[ Viresh: Fixed rebase conflict. ]
+Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
+---
+ drivers/cpufreq/cpufreq-dt-platdev.c | 1 +
+ drivers/cpufreq/qcom-cpufreq-nvmem.c | 58 ++++++++++++++++++++++++++++
+ 2 files changed, 59 insertions(+)
+
+--- a/drivers/cpufreq/cpufreq-dt-platdev.c
++++ b/drivers/cpufreq/cpufreq-dt-platdev.c
+@@ -163,6 +163,7 @@ static const struct of_device_id blockli
+ { .compatible = "ti,dra7", },
+ { .compatible = "ti,omap3", },
+
++ { .compatible = "qcom,ipq6018", },
+ { .compatible = "qcom,ipq8064", },
+ { .compatible = "qcom,apq8064", },
+ { .compatible = "qcom,msm8974", },
+--- a/drivers/cpufreq/qcom-cpufreq-nvmem.c
++++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c
+@@ -31,6 +31,8 @@
+
+ #include <dt-bindings/arm/qcom,ids.h>
+
++#define IPQ6000_VERSION BIT(2)
++
+ struct qcom_cpufreq_drv;
+
+ struct qcom_cpufreq_match_data {
+@@ -204,6 +206,57 @@ len_error:
+ return ret;
+ }
+
++static int qcom_cpufreq_ipq6018_name_version(struct device *cpu_dev,
++ struct nvmem_cell *speedbin_nvmem,
++ char **pvs_name,
++ struct qcom_cpufreq_drv *drv)
++{
++ u32 msm_id;
++ int ret;
++ u8 *speedbin;
++ *pvs_name = NULL;
++
++ ret = qcom_smem_get_soc_id(&msm_id);
++ if (ret)
++ return ret;
++
++ speedbin = nvmem_cell_read(speedbin_nvmem, NULL);
++ if (IS_ERR(speedbin))
++ return PTR_ERR(speedbin);
++
++ switch (msm_id) {
++ case QCOM_ID_IPQ6005:
++ case QCOM_ID_IPQ6010:
++ case QCOM_ID_IPQ6018:
++ case QCOM_ID_IPQ6028:
++ /* Fuse Value Freq BIT to set
++ * ---------------------------------
++ * 2’b0 No Limit BIT(0)
++ * 2’b1 1.5 GHz BIT(1)
++ */
++ drv->versions = 1 << (unsigned int)(*speedbin);
++ break;
++ case QCOM_ID_IPQ6000:
++ /*
++ * IPQ6018 family only has one bit to advertise the CPU
++ * speed-bin, but that is not enough for IPQ6000 which
++ * is only rated up to 1.2GHz.
++ * So for IPQ6000 manually set BIT(2) based on SMEM ID.
++ */
++ drv->versions = IPQ6000_VERSION;
++ break;
++ default:
++ dev_err(cpu_dev,
++ "SoC ID %u is not part of IPQ6018 family, limiting to 1.2GHz!\n",
++ msm_id);
++ drv->versions = IPQ6000_VERSION;
++ break;
++ }
++
++ kfree(speedbin);
++ return 0;
++}
++
+ static const struct qcom_cpufreq_match_data match_data_kryo = {
+ .get_version = qcom_cpufreq_kryo_name_version,
+ };
+@@ -218,6 +271,10 @@ static const struct qcom_cpufreq_match_d
+ .genpd_names = qcs404_genpd_names,
+ };
+
++static const struct qcom_cpufreq_match_data match_data_ipq6018 = {
++ .get_version = qcom_cpufreq_ipq6018_name_version,
++};
++
+ static int qcom_cpufreq_probe(struct platform_device *pdev)
+ {
+ struct qcom_cpufreq_drv *drv;
+@@ -362,6 +419,7 @@ static const struct of_device_id qcom_cp
+ { .compatible = "qcom,apq8096", .data = &match_data_kryo },
+ { .compatible = "qcom,msm8996", .data = &match_data_kryo },
+ { .compatible = "qcom,qcs404", .data = &match_data_qcs404 },
++ { .compatible = "qcom,ipq6018", .data = &match_data_ipq6018 },
+ { .compatible = "qcom,ipq8064", .data = &match_data_krait },
+ { .compatible = "qcom,apq8064", .data = &match_data_krait },
+ { .compatible = "qcom,msm8974", .data = &match_data_krait },
diff --git a/target/linux/qualcommax/patches-6.1/0026-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ8074.patch b/target/linux/qualcommax/patches-6.1/0026-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ8074.patch
new file mode 100644
index 0000000000..cbd7b770f4
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0026-v6.7-cpufreq-qcom-nvmem-add-support-for-IPQ8074.patch
@@ -0,0 +1,113 @@
+From 0b9cd949136f1b63f7aa9424b6e583a1ab261e36 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 13 Oct 2023 19:20:02 +0200
+Subject: [PATCH] cpufreq: qcom-nvmem: add support for IPQ8074
+
+IPQ8074 comes in 3 families:
+* IPQ8070A/IPQ8071A (Acorn) up to 1.4GHz
+* IPQ8172/IPQ8173/IPQ8174 (Oak) up to 1.4GHz
+* IPQ8072A/IPQ8074A/IPQ8076A/IPQ8078A (Hawkeye) up to 2.2GHz
+
+So, in order to be able to share one OPP table lets add support for IPQ8074
+family based of SMEM SoC ID-s as speedbin fuse is always 0 on IPQ8074.
+
+IPQ8074 compatible is blacklisted from DT platdev as the cpufreq device
+will get created by NVMEM CPUFreq driver.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Acked-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+[ Viresh: Fixed rebase conflict. ]
+Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
+---
+ drivers/cpufreq/cpufreq-dt-platdev.c | 1 +
+ drivers/cpufreq/qcom-cpufreq-nvmem.c | 48 ++++++++++++++++++++++++++++
+ 2 files changed, 49 insertions(+)
+
+--- a/drivers/cpufreq/cpufreq-dt-platdev.c
++++ b/drivers/cpufreq/cpufreq-dt-platdev.c
+@@ -165,6 +165,7 @@ static const struct of_device_id blockli
+
+ { .compatible = "qcom,ipq6018", },
+ { .compatible = "qcom,ipq8064", },
++ { .compatible = "qcom,ipq8074", },
+ { .compatible = "qcom,apq8064", },
+ { .compatible = "qcom,msm8974", },
+ { .compatible = "qcom,msm8960", },
+--- a/drivers/cpufreq/qcom-cpufreq-nvmem.c
++++ b/drivers/cpufreq/qcom-cpufreq-nvmem.c
+@@ -33,6 +33,11 @@
+
+ #define IPQ6000_VERSION BIT(2)
+
++enum ipq8074_versions {
++ IPQ8074_HAWKEYE_VERSION = 0,
++ IPQ8074_ACORN_VERSION,
++};
++
+ struct qcom_cpufreq_drv;
+
+ struct qcom_cpufreq_match_data {
+@@ -257,6 +262,44 @@ static int qcom_cpufreq_ipq6018_name_ver
+ return 0;
+ }
+
++static int qcom_cpufreq_ipq8074_name_version(struct device *cpu_dev,
++ struct nvmem_cell *speedbin_nvmem,
++ char **pvs_name,
++ struct qcom_cpufreq_drv *drv)
++{
++ u32 msm_id;
++ int ret;
++ *pvs_name = NULL;
++
++ ret = qcom_smem_get_soc_id(&msm_id);
++ if (ret)
++ return ret;
++
++ switch (msm_id) {
++ case QCOM_ID_IPQ8070A:
++ case QCOM_ID_IPQ8071A:
++ case QCOM_ID_IPQ8172:
++ case QCOM_ID_IPQ8173:
++ case QCOM_ID_IPQ8174:
++ drv->versions = BIT(IPQ8074_ACORN_VERSION);
++ break;
++ case QCOM_ID_IPQ8072A:
++ case QCOM_ID_IPQ8074A:
++ case QCOM_ID_IPQ8076A:
++ case QCOM_ID_IPQ8078A:
++ drv->versions = BIT(IPQ8074_HAWKEYE_VERSION);
++ break;
++ default:
++ dev_err(cpu_dev,
++ "SoC ID %u is not part of IPQ8074 family, limiting to 1.4GHz!\n",
++ msm_id);
++ drv->versions = BIT(IPQ8074_ACORN_VERSION);
++ break;
++ }
++
++ return 0;
++}
++
+ static const struct qcom_cpufreq_match_data match_data_kryo = {
+ .get_version = qcom_cpufreq_kryo_name_version,
+ };
+@@ -275,6 +318,10 @@ static const struct qcom_cpufreq_match_d
+ .get_version = qcom_cpufreq_ipq6018_name_version,
+ };
+
++static const struct qcom_cpufreq_match_data match_data_ipq8074 = {
++ .get_version = qcom_cpufreq_ipq8074_name_version,
++};
++
+ static int qcom_cpufreq_probe(struct platform_device *pdev)
+ {
+ struct qcom_cpufreq_drv *drv;
+@@ -421,6 +468,7 @@ static const struct of_device_id qcom_cp
+ { .compatible = "qcom,qcs404", .data = &match_data_qcs404 },
+ { .compatible = "qcom,ipq6018", .data = &match_data_ipq6018 },
+ { .compatible = "qcom,ipq8064", .data = &match_data_krait },
++ { .compatible = "qcom,ipq8074", .data = &match_data_ipq8074 },
+ { .compatible = "qcom,apq8064", .data = &match_data_krait },
+ { .compatible = "qcom,msm8974", .data = &match_data_krait },
+ { .compatible = "qcom,msm8960", .data = &match_data_krait },
diff --git a/target/linux/qualcommax/patches-6.1/0027-v6.7-clk-qcom-apss-ipq6018-add-the-GPLL0-clock-also-as-cl.patch b/target/linux/qualcommax/patches-6.1/0027-v6.7-clk-qcom-apss-ipq6018-add-the-GPLL0-clock-also-as-cl.patch
new file mode 100644
index 0000000000..ddd53f9d42
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0027-v6.7-clk-qcom-apss-ipq6018-add-the-GPLL0-clock-also-as-cl.patch
@@ -0,0 +1,43 @@
+From c917237a7cb17b97cc48e073881a9873f3caeaa2 Mon Sep 17 00:00:00 2001
+From: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+Date: Thu, 14 Sep 2023 12:29:57 +0530
+Subject: [PATCH] clk: qcom: apss-ipq6018: add the GPLL0 clock also as clock
+ provider
+
+While the kernel is booting up, APSS PLL will be running at 800MHz with
+GPLL0 as source. Once the cpufreq driver is available, APSS PLL will be
+configured and select the rate based on the opp table and the source will
+be changed to APSS_PLL_EARLY.
+
+Without this patch, CPU Freq driver reports that CPU is running at 24MHz
+instead of the 800MHz.
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Tested-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+---
+ drivers/clk/qcom/apss-ipq6018.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/clk/qcom/apss-ipq6018.c
++++ b/drivers/clk/qcom/apss-ipq6018.c
+@@ -20,16 +20,19 @@
+
+ enum {
+ P_XO,
++ P_GPLL0,
+ P_APSS_PLL_EARLY,
+ };
+
+ static const struct clk_parent_data parents_apcs_alias0_clk_src[] = {
+ { .fw_name = "xo" },
++ { .fw_name = "gpll0" },
+ { .fw_name = "pll" },
+ };
+
+ static const struct parent_map parents_apcs_alias0_clk_src_map[] = {
+ { P_XO, 0 },
++ { P_GPLL0, 4 },
+ { P_APSS_PLL_EARLY, 5 },
+ };
+
diff --git a/target/linux/qualcommax/patches-6.1/0028-v6.7-arm64-dts-qcom-ipq8074-include-the-GPLL0-as-clock-pr.patch b/target/linux/qualcommax/patches-6.1/0028-v6.7-arm64-dts-qcom-ipq8074-include-the-GPLL0-as-clock-pr.patch
new file mode 100644
index 0000000000..fb7011de95
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0028-v6.7-arm64-dts-qcom-ipq8074-include-the-GPLL0-as-clock-pr.patch
@@ -0,0 +1,32 @@
+From 3b48a7d925a757b3fa53c04baaf68bb8313c3ffb Mon Sep 17 00:00:00 2001
+From: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+Date: Thu, 14 Sep 2023 12:29:58 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq8074: include the GPLL0 as clock
+ provider for mailbox
+
+While the kernel is booting up, APSS PLL will be running at 800MHz with
+GPLL0 as source. Once the cpufreq driver is available, APSS PLL will be
+configured to the rate based on the opp table and the source also will
+be changed to APSS_PLL_EARLY. So allow the mailbox to consume the GPLL0,
+with this inclusion, CPU Freq correctly reports that CPU is running at
+800MHz rather than 24MHz.
+
+Signed-off-by: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -685,8 +685,8 @@
+ compatible = "qcom,ipq8074-apcs-apps-global",
+ "qcom,ipq6018-apcs-apps-global";
+ reg = <0x0b111000 0x1000>;
+- clocks = <&a53pll>, <&xo>;
+- clock-names = "pll", "xo";
++ clocks = <&a53pll>, <&xo>, <&gcc GPLL0>;
++ clock-names = "pll", "xo", "gpll0";
+
+ #clock-cells = <1>;
+ #mbox-cells = <1>;
diff --git a/target/linux/qualcommax/patches-6.1/0029-v6.3-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ5332-and-its.patch b/target/linux/qualcommax/patches-6.1/0029-v6.3-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ5332-and-its.patch
new file mode 100644
index 0000000000..61bb3f6c37
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0029-v6.3-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ5332-and-its.patch
@@ -0,0 +1,27 @@
+From c0877a26b7ee54ef30d16ffdcdd37f2bcffe518e Mon Sep 17 00:00:00 2001
+From: Kathiravan T <quic_kathirav@quicinc.com>
+Date: Wed, 8 Feb 2023 11:27:08 +0530
+Subject: [PATCH] dt-bindings: arm: qcom,ids: Add IDs for IPQ5332 and its
+ variant
+
+Add SOC ID for Qualcomm IPQ5332 and IPQ5322 variants.
+
+Signed-off-by: Kathiravan T <quic_kathirav@quicinc.com>
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230208055709.13162-2-quic_kathirav@quicinc.com
+---
+ include/dt-bindings/arm/qcom,ids.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/dt-bindings/arm/qcom,ids.h
++++ b/include/dt-bindings/arm/qcom,ids.h
+@@ -143,6 +143,8 @@
+ #define QCOM_ID_SC7280 487
+ #define QCOM_ID_SC7180P 495
+ #define QCOM_ID_SM6375 507
++#define QCOM_ID_IPQ5332 592
++#define QCOM_ID_IPQ5322 593
+
+ /*
+ * The board type and revision information, used by Qualcomm bootloaders and
diff --git a/target/linux/qualcommax/patches-6.1/0030-v6.4-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ9574-and-its.patch b/target/linux/qualcommax/patches-6.1/0030-v6.4-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ9574-and-its.patch
new file mode 100644
index 0000000000..7d80e4c1fd
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0030-v6.4-dt-bindings-arm-qcom-ids-Add-IDs-for-IPQ9574-and-its.patch
@@ -0,0 +1,33 @@
+From 725352e15e1d030885611a546eb1f2884851a407 Mon Sep 17 00:00:00 2001
+From: Varadarajan Narayanan <quic_varada@quicinc.com>
+Date: Tue, 14 Mar 2023 11:43:33 +0530
+Subject: [PATCH] dt-bindings: arm: qcom,ids: Add IDs for IPQ9574 and its
+ variants
+
+Add SOC ID for Qualcomm IPQ9574, IPQ9570, IPQ9554, IPQ9550,
+IPQ9514 and IPQ9510
+
+Signed-off-by: Varadarajan Narayanan <quic_varada@quicinc.com>
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Kathiravan T <quic_kathirav@quicinc.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/1678774414-14414-2-git-send-email-quic_varada@quicinc.com
+---
+ include/dt-bindings/arm/qcom,ids.h | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/include/dt-bindings/arm/qcom,ids.h
++++ b/include/dt-bindings/arm/qcom,ids.h
+@@ -143,6 +143,12 @@
+ #define QCOM_ID_SC7280 487
+ #define QCOM_ID_SC7180P 495
+ #define QCOM_ID_SM6375 507
++#define QCOM_ID_IPQ9514 510
++#define QCOM_ID_IPQ9550 511
++#define QCOM_ID_IPQ9554 512
++#define QCOM_ID_IPQ9570 513
++#define QCOM_ID_IPQ9574 514
++#define QCOM_ID_IPQ9510 521
+ #define QCOM_ID_IPQ5332 592
+ #define QCOM_ID_IPQ5322 593
+
diff --git a/target/linux/qualcommax/patches-6.1/0031-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5312-and-.patch b/target/linux/qualcommax/patches-6.1/0031-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5312-and-.patch
new file mode 100644
index 0000000000..ad70e7b6c9
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0031-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5312-and-.patch
@@ -0,0 +1,28 @@
+From 614c778cf0d570642c50715adfa0b70930d8cf29 Mon Sep 17 00:00:00 2001
+From: Kathiravan T <quic_kathirav@quicinc.com>
+Date: Tue, 9 May 2023 09:05:30 +0530
+Subject: [PATCH] dt-bindings: arm: qcom,ids: add SoC ID for IPQ5312 and
+ IPQ5302
+
+Add the SoC ID for IPQ5312 and IPQ5302, which belong to the family of
+IPQ5332 SoC.
+
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Kathiravan T <quic_kathirav@quicinc.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230509033531.21468-2-quic_kathirav@quicinc.com
+---
+ include/dt-bindings/arm/qcom,ids.h | 2 ++
+ 1 file changed, 2 insertions(+)
+
+--- a/include/dt-bindings/arm/qcom,ids.h
++++ b/include/dt-bindings/arm/qcom,ids.h
+@@ -151,6 +151,8 @@
+ #define QCOM_ID_IPQ9510 521
+ #define QCOM_ID_IPQ5332 592
+ #define QCOM_ID_IPQ5322 593
++#define QCOM_ID_IPQ5312 594
++#define QCOM_ID_IPQ5302 595
+
+ /*
+ * The board type and revision information, used by Qualcomm bootloaders and
diff --git a/target/linux/qualcommax/patches-6.1/0032-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5300.patch b/target/linux/qualcommax/patches-6.1/0032-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5300.patch
new file mode 100644
index 0000000000..7925a0994d
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0032-v6.5-dt-bindings-arm-qcom-ids-add-SoC-ID-for-IPQ5300.patch
@@ -0,0 +1,25 @@
+From b3c72f2795467e3d43ee429b0ebd5f523ec08f60 Mon Sep 17 00:00:00 2001
+From: Kathiravan T <quic_kathirav@quicinc.com>
+Date: Mon, 5 Jun 2023 13:35:28 +0530
+Subject: [PATCH] dt-bindings: arm: qcom,ids: add SoC ID for IPQ5300
+
+Add the SoC ID for IPQ5300, which belong to the family of IPQ5332 SoC.
+
+Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Signed-off-by: Kathiravan T <quic_kathirav@quicinc.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230605080531.3879-2-quic_kathirav@quicinc.com
+---
+ include/dt-bindings/arm/qcom,ids.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/include/dt-bindings/arm/qcom,ids.h
++++ b/include/dt-bindings/arm/qcom,ids.h
+@@ -153,6 +153,7 @@
+ #define QCOM_ID_IPQ5322 593
+ #define QCOM_ID_IPQ5312 594
+ #define QCOM_ID_IPQ5302 595
++#define QCOM_ID_IPQ5300 624
+
+ /*
+ * The board type and revision information, used by Qualcomm bootloaders and
diff --git a/target/linux/qualcommax/patches-6.1/0033-v6.2-arm64-dts-qcom-ipq6018-move-ARMv8-timer-out-of-SoC.patch b/target/linux/qualcommax/patches-6.1/0033-v6.2-arm64-dts-qcom-ipq6018-move-ARMv8-timer-out-of-SoC.patch
new file mode 100644
index 0000000000..c3e94a2ae0
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0033-v6.2-arm64-dts-qcom-ipq6018-move-ARMv8-timer-out-of-SoC.patch
@@ -0,0 +1,52 @@
+From feeef118fda562cf9081edef8ad464d89db070f4 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Tue, 27 Sep 2022 22:12:18 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: move ARMv8 timer out of SoC node
+
+The ARM timer is usually considered not part of SoC node, just like
+other ARM designed blocks (PMU, PSCI). This fixes dtbs_check warning:
+
+arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dtb: soc: timer: {'compatible': ['arm,armv8-timer'], 'interrupts': [[1, 2, 3848], [1, 3, 3848], [1, 4, 3848], [1, 1, 3848]]} should not be valid under {'type': 'object'}
+ From schema: dtschema/schemas/simple-bus.yaml
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20220927201218.1264506-2-robimarko@gmail.com
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 16 ++++++++--------
+ 1 file changed, 8 insertions(+), 8 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -510,14 +510,6 @@
+ clock-names = "xo";
+ };
+
+- timer {
+- compatible = "arm,armv8-timer";
+- interrupts = <GIC_PPI 2 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+- <GIC_PPI 3 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+- <GIC_PPI 4 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
+- <GIC_PPI 1 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
+- };
+-
+ timer@b120000 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+@@ -769,6 +761,14 @@
+ };
+ };
+
++ timer {
++ compatible = "arm,armv8-timer";
++ interrupts = <GIC_PPI 2 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
++ <GIC_PPI 3 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
++ <GIC_PPI 4 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
++ <GIC_PPI 1 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
++ };
++
+ wcss: wcss-smp2p {
+ compatible = "qcom,smp2p";
+ qcom,smem = <435>, <428>;
diff --git a/target/linux/qualcommax/patches-6.1/0034-v6.3-arm64-dts-qcom-ipq6018-Sort-nodes-properly.patch b/target/linux/qualcommax/patches-6.1/0034-v6.3-arm64-dts-qcom-ipq6018-Sort-nodes-properly.patch
new file mode 100644
index 0000000000..ad32e64fe1
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0034-v6.3-arm64-dts-qcom-ipq6018-Sort-nodes-properly.patch
@@ -0,0 +1,605 @@
+From 2c6e322a41c5e1ca45be50b9d5fbcda62dc23a0d Mon Sep 17 00:00:00 2001
+From: Konrad Dybcio <konrad.dybcio@linaro.org>
+Date: Mon, 2 Jan 2023 10:46:28 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq6018: Sort nodes properly
+
+Order nodes by unit address if one exists and alphabetically otherwise.
+
+Signed-off-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230102094642.74254-4-konrad.dybcio@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 562 +++++++++++++-------------
+ 1 file changed, 281 insertions(+), 281 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -87,6 +87,12 @@
+ };
+ };
+
++ firmware {
++ scm {
++ compatible = "qcom,scm-ipq6018", "qcom,scm";
++ };
++ };
++
+ cpu_opp_table: opp-table-cpu {
+ compatible = "operating-points-v2";
+ opp-shared;
+@@ -123,12 +129,6 @@
+ };
+ };
+
+- firmware {
+- scm {
+- compatible = "qcom,scm-ipq6018", "qcom,scm";
+- };
+- };
+-
+ pmuv8: pmu {
+ compatible = "arm,cortex-a53-pmu";
+ interrupts = <GIC_PPI 7 (GIC_CPU_MASK_SIMPLE(4) |
+@@ -166,6 +166,28 @@
+ };
+ };
+
++ rpm-glink {
++ compatible = "qcom,glink-rpm";
++ interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
++ qcom,rpm-msg-ram = <&rpm_msg_ram>;
++ mboxes = <&apcs_glb 0>;
++
++ rpm_requests: glink-channel {
++ compatible = "qcom,rpm-ipq6018";
++ qcom,glink-channels = "rpm_requests";
++
++ regulators {
++ compatible = "qcom,rpm-mp5496-regulators";
++
++ ipq6018_s2: s2 {
++ regulator-min-microvolt = <725000>;
++ regulator-max-microvolt = <1062500>;
++ regulator-always-on;
++ };
++ };
++ };
++ };
++
+ smem {
+ compatible = "qcom,smem";
+ memory-region = <&smem_region>;
+@@ -179,6 +201,102 @@
+ dma-ranges;
+ compatible = "simple-bus";
+
++ qusb_phy_1: qusb@59000 {
++ compatible = "qcom,ipq6018-qusb2-phy";
++ reg = <0x0 0x00059000 0x0 0x180>;
++ #phy-cells = <0>;
++
++ clocks = <&gcc GCC_USB1_PHY_CFG_AHB_CLK>,
++ <&xo>;
++ clock-names = "cfg_ahb", "ref";
++
++ resets = <&gcc GCC_QUSB2_1_PHY_BCR>;
++ status = "disabled";
++ };
++
++ ssphy_0: ssphy@78000 {
++ compatible = "qcom,ipq6018-qmp-usb3-phy";
++ reg = <0x0 0x00078000 0x0 0x1c4>;
++ #address-cells = <2>;
++ #size-cells = <2>;
++ ranges;
++
++ clocks = <&gcc GCC_USB0_AUX_CLK>,
++ <&gcc GCC_USB0_PHY_CFG_AHB_CLK>, <&xo>;
++ clock-names = "aux", "cfg_ahb", "ref";
++
++ resets = <&gcc GCC_USB0_PHY_BCR>,
++ <&gcc GCC_USB3PHY_0_PHY_BCR>;
++ reset-names = "phy","common";
++ status = "disabled";
++
++ usb0_ssphy: phy@78200 {
++ reg = <0x0 0x00078200 0x0 0x130>, /* Tx */
++ <0x0 0x00078400 0x0 0x200>, /* Rx */
++ <0x0 0x00078800 0x0 0x1f8>, /* PCS */
++ <0x0 0x00078600 0x0 0x044>; /* PCS misc */
++ #phy-cells = <0>;
++ #clock-cells = <0>;
++ clocks = <&gcc GCC_USB0_PIPE_CLK>;
++ clock-names = "pipe0";
++ clock-output-names = "gcc_usb0_pipe_clk_src";
++ };
++ };
++
++ qusb_phy_0: qusb@79000 {
++ compatible = "qcom,ipq6018-qusb2-phy";
++ reg = <0x0 0x00079000 0x0 0x180>;
++ #phy-cells = <0>;
++
++ clocks = <&gcc GCC_USB0_PHY_CFG_AHB_CLK>,
++ <&xo>;
++ clock-names = "cfg_ahb", "ref";
++
++ resets = <&gcc GCC_QUSB2_0_PHY_BCR>;
++ status = "disabled";
++ };
++
++ pcie_phy: phy@84000 {
++ compatible = "qcom,ipq6018-qmp-pcie-phy";
++ reg = <0x0 0x00084000 0x0 0x1bc>; /* Serdes PLL */
++ status = "disabled";
++ #address-cells = <2>;
++ #size-cells = <2>;
++ ranges;
++
++ clocks = <&gcc GCC_PCIE0_AUX_CLK>,
++ <&gcc GCC_PCIE0_AHB_CLK>;
++ clock-names = "aux", "cfg_ahb";
++
++ resets = <&gcc GCC_PCIE0_PHY_BCR>,
++ <&gcc GCC_PCIE0PHY_PHY_BCR>;
++ reset-names = "phy",
++ "common";
++
++ pcie_phy0: phy@84200 {
++ reg = <0x0 0x00084200 0x0 0x16c>, /* Serdes Tx */
++ <0x0 0x00084400 0x0 0x200>, /* Serdes Rx */
++ <0x0 0x00084800 0x0 0x1f0>, /* PCS: Lane0, COM, PCIE */
++ <0x0 0x00084c00 0x0 0xf4>; /* pcs_misc */
++ #phy-cells = <0>;
++
++ clocks = <&gcc GCC_PCIE0_PIPE_CLK>;
++ clock-names = "pipe0";
++ clock-output-names = "gcc_pcie0_pipe_clk_src";
++ #clock-cells = <0>;
++ };
++ };
++
++ mdio: mdio@90000 {
++ #address-cells = <1>;
++ #size-cells = <0>;
++ compatible = "qcom,ipq6018-mdio", "qcom,ipq4019-mdio";
++ reg = <0x0 0x00090000 0x0 0x64>;
++ clocks = <&gcc GCC_MDIO_AHB_CLK>;
++ clock-names = "gcc_mdio_ahb_clk";
++ status = "disabled";
++ };
++
+ prng: qrng@e1000 {
+ compatible = "qcom,prng-ee";
+ reg = <0x0 0x000e3000 0x0 0x1000>;
+@@ -257,6 +375,41 @@
+ reg = <0x0 0x01937000 0x0 0x21000>;
+ };
+
++ usb2: usb@70f8800 {
++ compatible = "qcom,ipq6018-dwc3", "qcom,dwc3";
++ reg = <0x0 0x070F8800 0x0 0x400>;
++ #address-cells = <2>;
++ #size-cells = <2>;
++ ranges;
++ clocks = <&gcc GCC_USB1_MASTER_CLK>,
++ <&gcc GCC_USB1_SLEEP_CLK>,
++ <&gcc GCC_USB1_MOCK_UTMI_CLK>;
++ clock-names = "core",
++ "sleep",
++ "mock_utmi";
++
++ assigned-clocks = <&gcc GCC_USB1_MASTER_CLK>,
++ <&gcc GCC_USB1_MOCK_UTMI_CLK>;
++ assigned-clock-rates = <133330000>,
++ <24000000>;
++ resets = <&gcc GCC_USB1_BCR>;
++ status = "disabled";
++
++ dwc_1: usb@7000000 {
++ compatible = "snps,dwc3";
++ reg = <0x0 0x07000000 0x0 0xcd00>;
++ interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;
++ phys = <&qusb_phy_1>;
++ phy-names = "usb2-phy";
++ tx-fifo-resize;
++ snps,is-utmi-l1-suspend;
++ snps,hird-threshold = /bits/ 8 <0x0>;
++ snps,dis_u2_susphy_quirk;
++ snps,dis_u3_susphy_quirk;
++ dr_mode = "host";
++ };
++ };
++
+ blsp_dma: dma-controller@7884000 {
+ compatible = "qcom,bam-v1.7.0";
+ reg = <0x0 0x07884000 0x0 0x2b000>;
+@@ -366,6 +519,49 @@
+ status = "disabled";
+ };
+
++ usb3: usb@8af8800 {
++ compatible = "qcom,ipq6018-dwc3", "qcom,dwc3";
++ reg = <0x0 0x08af8800 0x0 0x400>;
++ #address-cells = <2>;
++ #size-cells = <2>;
++ ranges;
++
++ clocks = <&gcc GCC_SYS_NOC_USB0_AXI_CLK>,
++ <&gcc GCC_USB0_MASTER_CLK>,
++ <&gcc GCC_USB0_SLEEP_CLK>,
++ <&gcc GCC_USB0_MOCK_UTMI_CLK>;
++ clock-names = "cfg_noc",
++ "core",
++ "sleep",
++ "mock_utmi";
++
++ assigned-clocks = <&gcc GCC_SYS_NOC_USB0_AXI_CLK>,
++ <&gcc GCC_USB0_MASTER_CLK>,
++ <&gcc GCC_USB0_MOCK_UTMI_CLK>;
++ assigned-clock-rates = <133330000>,
++ <133330000>,
++ <24000000>;
++
++ resets = <&gcc GCC_USB0_BCR>;
++ status = "disabled";
++
++ dwc_0: usb@8a00000 {
++ compatible = "snps,dwc3";
++ reg = <0x0 0x08a00000 0x0 0xcd00>;
++ interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
++ phys = <&qusb_phy_0>, <&usb0_ssphy>;
++ phy-names = "usb2-phy", "usb3-phy";
++ clocks = <&xo>;
++ clock-names = "ref";
++ tx-fifo-resize;
++ snps,is-utmi-l1-suspend;
++ snps,hird-threshold = /bits/ 8 <0x0>;
++ snps,dis_u2_susphy_quirk;
++ snps,dis_u3_susphy_quirk;
++ dr_mode = "host";
++ };
++ };
++
+ intc: interrupt-controller@b000000 {
+ compatible = "qcom,msm-qgic2";
+ #address-cells = <2>;
+@@ -386,105 +582,6 @@
+ };
+ };
+
+- pcie_phy: phy@84000 {
+- compatible = "qcom,ipq6018-qmp-pcie-phy";
+- reg = <0x0 0x00084000 0x0 0x1bc>; /* Serdes PLL */
+- status = "disabled";
+- #address-cells = <2>;
+- #size-cells = <2>;
+- ranges;
+-
+- clocks = <&gcc GCC_PCIE0_AUX_CLK>,
+- <&gcc GCC_PCIE0_AHB_CLK>;
+- clock-names = "aux", "cfg_ahb";
+-
+- resets = <&gcc GCC_PCIE0_PHY_BCR>,
+- <&gcc GCC_PCIE0PHY_PHY_BCR>;
+- reset-names = "phy",
+- "common";
+-
+- pcie_phy0: phy@84200 {
+- reg = <0x0 0x00084200 0x0 0x16c>, /* Serdes Tx */
+- <0x0 0x00084400 0x0 0x200>, /* Serdes Rx */
+- <0x0 0x00084800 0x0 0x1f0>, /* PCS: Lane0, COM, PCIE */
+- <0x0 0x00084c00 0x0 0xf4>; /* pcs_misc */
+- #phy-cells = <0>;
+-
+- clocks = <&gcc GCC_PCIE0_PIPE_CLK>;
+- clock-names = "pipe0";
+- clock-output-names = "gcc_pcie0_pipe_clk_src";
+- #clock-cells = <0>;
+- };
+- };
+-
+- pcie0: pci@20000000 {
+- compatible = "qcom,pcie-ipq6018";
+- reg = <0x0 0x20000000 0x0 0xf1d>,
+- <0x0 0x20000f20 0x0 0xa8>,
+- <0x0 0x20001000 0x0 0x1000>,
+- <0x0 0x80000 0x0 0x4000>,
+- <0x0 0x20100000 0x0 0x1000>;
+- reg-names = "dbi", "elbi", "atu", "parf", "config";
+-
+- device_type = "pci";
+- linux,pci-domain = <0>;
+- bus-range = <0x00 0xff>;
+- num-lanes = <1>;
+- max-link-speed = <3>;
+- #address-cells = <3>;
+- #size-cells = <2>;
+-
+- phys = <&pcie_phy0>;
+- phy-names = "pciephy";
+-
+- ranges = <0x81000000 0x0 0x00000000 0x0 0x20200000 0x0 0x10000>,
+- <0x82000000 0x0 0x20220000 0x0 0x20220000 0x0 0xfde0000>;
+-
+- interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
+- interrupt-names = "msi";
+-
+- #interrupt-cells = <1>;
+- interrupt-map-mask = <0 0 0 0x7>;
+- interrupt-map = <0 0 0 1 &intc 0 75
+- IRQ_TYPE_LEVEL_HIGH>, /* int_a */
+- <0 0 0 2 &intc 0 78
+- IRQ_TYPE_LEVEL_HIGH>, /* int_b */
+- <0 0 0 3 &intc 0 79
+- IRQ_TYPE_LEVEL_HIGH>, /* int_c */
+- <0 0 0 4 &intc 0 83
+- IRQ_TYPE_LEVEL_HIGH>; /* int_d */
+-
+- clocks = <&gcc GCC_SYS_NOC_PCIE0_AXI_CLK>,
+- <&gcc GCC_PCIE0_AXI_M_CLK>,
+- <&gcc GCC_PCIE0_AXI_S_CLK>,
+- <&gcc GCC_PCIE0_AXI_S_BRIDGE_CLK>,
+- <&gcc PCIE0_RCHNG_CLK>;
+- clock-names = "iface",
+- "axi_m",
+- "axi_s",
+- "axi_bridge",
+- "rchng";
+-
+- resets = <&gcc GCC_PCIE0_PIPE_ARES>,
+- <&gcc GCC_PCIE0_SLEEP_ARES>,
+- <&gcc GCC_PCIE0_CORE_STICKY_ARES>,
+- <&gcc GCC_PCIE0_AXI_MASTER_ARES>,
+- <&gcc GCC_PCIE0_AXI_SLAVE_ARES>,
+- <&gcc GCC_PCIE0_AHB_ARES>,
+- <&gcc GCC_PCIE0_AXI_MASTER_STICKY_ARES>,
+- <&gcc GCC_PCIE0_AXI_SLAVE_STICKY_ARES>;
+- reset-names = "pipe",
+- "sleep",
+- "sticky",
+- "axi_m",
+- "axi_s",
+- "ahb",
+- "axi_m_sticky",
+- "axi_s_sticky";
+-
+- status = "disabled";
+- };
+-
+ watchdog@b017000 {
+ compatible = "qcom,kpss-wdt";
+ interrupts = <GIC_SPI 3 IRQ_TYPE_EDGE_RISING>;
+@@ -617,147 +714,74 @@
+ };
+ };
+
+- mdio: mdio@90000 {
+- #address-cells = <1>;
+- #size-cells = <0>;
+- compatible = "qcom,ipq6018-mdio", "qcom,ipq4019-mdio";
+- reg = <0x0 0x00090000 0x0 0x64>;
+- clocks = <&gcc GCC_MDIO_AHB_CLK>;
+- clock-names = "gcc_mdio_ahb_clk";
+- status = "disabled";
+- };
+-
+- qusb_phy_1: qusb@59000 {
+- compatible = "qcom,ipq6018-qusb2-phy";
+- reg = <0x0 0x00059000 0x0 0x180>;
+- #phy-cells = <0>;
+-
+- clocks = <&gcc GCC_USB1_PHY_CFG_AHB_CLK>,
+- <&xo>;
+- clock-names = "cfg_ahb", "ref";
+-
+- resets = <&gcc GCC_QUSB2_1_PHY_BCR>;
+- status = "disabled";
+- };
+-
+- usb2: usb@70f8800 {
+- compatible = "qcom,ipq6018-dwc3", "qcom,dwc3";
+- reg = <0x0 0x070F8800 0x0 0x400>;
+- #address-cells = <2>;
+- #size-cells = <2>;
+- ranges;
+- clocks = <&gcc GCC_USB1_MASTER_CLK>,
+- <&gcc GCC_USB1_SLEEP_CLK>,
+- <&gcc GCC_USB1_MOCK_UTMI_CLK>;
+- clock-names = "core",
+- "sleep",
+- "mock_utmi";
+-
+- assigned-clocks = <&gcc GCC_USB1_MASTER_CLK>,
+- <&gcc GCC_USB1_MOCK_UTMI_CLK>;
+- assigned-clock-rates = <133330000>,
+- <24000000>;
+- resets = <&gcc GCC_USB1_BCR>;
+- status = "disabled";
+-
+- dwc_1: usb@7000000 {
+- compatible = "snps,dwc3";
+- reg = <0x0 0x07000000 0x0 0xcd00>;
+- interrupts = <GIC_SPI 99 IRQ_TYPE_LEVEL_HIGH>;
+- phys = <&qusb_phy_1>;
+- phy-names = "usb2-phy";
+- tx-fifo-resize;
+- snps,is-utmi-l1-suspend;
+- snps,hird-threshold = /bits/ 8 <0x0>;
+- snps,dis_u2_susphy_quirk;
+- snps,dis_u3_susphy_quirk;
+- dr_mode = "host";
+- };
+- };
++ pcie0: pci@20000000 {
++ compatible = "qcom,pcie-ipq6018";
++ reg = <0x0 0x20000000 0x0 0xf1d>,
++ <0x0 0x20000f20 0x0 0xa8>,
++ <0x0 0x20001000 0x0 0x1000>,
++ <0x0 0x80000 0x0 0x4000>,
++ <0x0 0x20100000 0x0 0x1000>;
++ reg-names = "dbi", "elbi", "atu", "parf", "config";
+
+- ssphy_0: ssphy@78000 {
+- compatible = "qcom,ipq6018-qmp-usb3-phy";
+- reg = <0x0 0x00078000 0x0 0x1c4>;
+- #address-cells = <2>;
++ device_type = "pci";
++ linux,pci-domain = <0>;
++ bus-range = <0x00 0xff>;
++ num-lanes = <1>;
++ max-link-speed = <3>;
++ #address-cells = <3>;
+ #size-cells = <2>;
+- ranges;
+-
+- clocks = <&gcc GCC_USB0_AUX_CLK>,
+- <&gcc GCC_USB0_PHY_CFG_AHB_CLK>, <&xo>;
+- clock-names = "aux", "cfg_ahb", "ref";
+-
+- resets = <&gcc GCC_USB0_PHY_BCR>,
+- <&gcc GCC_USB3PHY_0_PHY_BCR>;
+- reset-names = "phy","common";
+- status = "disabled";
+-
+- usb0_ssphy: phy@78200 {
+- reg = <0x0 0x00078200 0x0 0x130>, /* Tx */
+- <0x0 0x00078400 0x0 0x200>, /* Rx */
+- <0x0 0x00078800 0x0 0x1f8>, /* PCS */
+- <0x0 0x00078600 0x0 0x044>; /* PCS misc */
+- #phy-cells = <0>;
+- #clock-cells = <0>;
+- clocks = <&gcc GCC_USB0_PIPE_CLK>;
+- clock-names = "pipe0";
+- clock-output-names = "gcc_usb0_pipe_clk_src";
+- };
+- };
+
+- qusb_phy_0: qusb@79000 {
+- compatible = "qcom,ipq6018-qusb2-phy";
+- reg = <0x0 0x00079000 0x0 0x180>;
+- #phy-cells = <0>;
++ phys = <&pcie_phy0>;
++ phy-names = "pciephy";
+
+- clocks = <&gcc GCC_USB0_PHY_CFG_AHB_CLK>,
+- <&xo>;
+- clock-names = "cfg_ahb", "ref";
++ ranges = <0x81000000 0 0x20200000 0 0x20200000
++ 0 0x10000>, /* downstream I/O */
++ <0x82000000 0 0x20220000 0 0x20220000
++ 0 0xfde0000>; /* non-prefetchable memory */
+
+- resets = <&gcc GCC_QUSB2_0_PHY_BCR>;
+- status = "disabled";
+- };
++ interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "msi";
+
+- usb3: usb@8af8800 {
+- compatible = "qcom,ipq6018-dwc3", "qcom,dwc3";
+- reg = <0x0 0x8af8800 0x0 0x400>;
+- #address-cells = <2>;
+- #size-cells = <2>;
+- ranges;
++ #interrupt-cells = <1>;
++ interrupt-map-mask = <0 0 0 0x7>;
++ interrupt-map = <0 0 0 1 &intc 0 75
++ IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++ <0 0 0 2 &intc 0 78
++ IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++ <0 0 0 3 &intc 0 79
++ IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++ <0 0 0 4 &intc 0 83
++ IRQ_TYPE_LEVEL_HIGH>; /* int_d */
+
+- clocks = <&gcc GCC_SYS_NOC_USB0_AXI_CLK>,
+- <&gcc GCC_USB0_MASTER_CLK>,
+- <&gcc GCC_USB0_SLEEP_CLK>,
+- <&gcc GCC_USB0_MOCK_UTMI_CLK>;
+- clock-names = "cfg_noc",
+- "core",
+- "sleep",
+- "mock_utmi";
++ clocks = <&gcc GCC_SYS_NOC_PCIE0_AXI_CLK>,
++ <&gcc GCC_PCIE0_AXI_M_CLK>,
++ <&gcc GCC_PCIE0_AXI_S_CLK>,
++ <&gcc GCC_PCIE0_AXI_S_BRIDGE_CLK>,
++ <&gcc PCIE0_RCHNG_CLK>;
++ clock-names = "iface",
++ "axi_m",
++ "axi_s",
++ "axi_bridge",
++ "rchng";
+
+- assigned-clocks = <&gcc GCC_SYS_NOC_USB0_AXI_CLK>,
+- <&gcc GCC_USB0_MASTER_CLK>,
+- <&gcc GCC_USB0_MOCK_UTMI_CLK>;
+- assigned-clock-rates = <133330000>,
+- <133330000>,
+- <24000000>;
++ resets = <&gcc GCC_PCIE0_PIPE_ARES>,
++ <&gcc GCC_PCIE0_SLEEP_ARES>,
++ <&gcc GCC_PCIE0_CORE_STICKY_ARES>,
++ <&gcc GCC_PCIE0_AXI_MASTER_ARES>,
++ <&gcc GCC_PCIE0_AXI_SLAVE_ARES>,
++ <&gcc GCC_PCIE0_AHB_ARES>,
++ <&gcc GCC_PCIE0_AXI_MASTER_STICKY_ARES>,
++ <&gcc GCC_PCIE0_AXI_SLAVE_STICKY_ARES>;
++ reset-names = "pipe",
++ "sleep",
++ "sticky",
++ "axi_m",
++ "axi_s",
++ "ahb",
++ "axi_m_sticky",
++ "axi_s_sticky";
+
+- resets = <&gcc GCC_USB0_BCR>;
+ status = "disabled";
+-
+- dwc_0: usb@8a00000 {
+- compatible = "snps,dwc3";
+- reg = <0x0 0x8a00000 0x0 0xcd00>;
+- interrupts = <GIC_SPI 140 IRQ_TYPE_LEVEL_HIGH>;
+- phys = <&qusb_phy_0>, <&usb0_ssphy>;
+- phy-names = "usb2-phy", "usb3-phy";
+- clocks = <&xo>;
+- clock-names = "ref";
+- tx-fifo-resize;
+- snps,is-utmi-l1-suspend;
+- snps,hird-threshold = /bits/ 8 <0x0>;
+- snps,dis_u2_susphy_quirk;
+- snps,dis_u3_susphy_quirk;
+- dr_mode = "host";
+- };
+ };
+ };
+
+@@ -792,26 +816,4 @@
+ #interrupt-cells = <2>;
+ };
+ };
+-
+- rpm-glink {
+- compatible = "qcom,glink-rpm";
+- interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
+- qcom,rpm-msg-ram = <&rpm_msg_ram>;
+- mboxes = <&apcs_glb 0>;
+-
+- rpm_requests: glink-channel {
+- compatible = "qcom,rpm-ipq6018";
+- qcom,glink-channels = "rpm_requests";
+-
+- regulators {
+- compatible = "qcom,rpm-mp5496-regulators";
+-
+- ipq6018_s2: s2 {
+- regulator-min-microvolt = <725000>;
+- regulator-max-microvolt = <1062500>;
+- regulator-always-on;
+- };
+- };
+- };
+- };
+ };
diff --git a/target/linux/qualcommax/patches-6.1/0035-v6.3-arm64-dts-qcom-ipq6018-Add-remove-some-newlines.patch b/target/linux/qualcommax/patches-6.1/0035-v6.3-arm64-dts-qcom-ipq6018-Add-remove-some-newlines.patch
new file mode 100644
index 0000000000..a883e305df
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0035-v6.3-arm64-dts-qcom-ipq6018-Add-remove-some-newlines.patch
@@ -0,0 +1,92 @@
+From 6db9ed9a128cbae1423d043f3debd8bfa77783fd Mon Sep 17 00:00:00 2001
+From: Konrad Dybcio <konrad.dybcio@linaro.org>
+Date: Mon, 2 Jan 2023 10:46:29 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq6018: Add/remove some newlines
+
+Some lines were broken very aggresively, presumably to fit under 80 chars
+and some places could have used a newline, particularly between subsequent
+nodes. Address all that and remove redundant comments near PCIe ranges
+while at it so as not to exceed 100 chars needlessly.
+
+Signed-off-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230102094642.74254-5-konrad.dybcio@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 26 ++++++++++++--------------
+ 1 file changed, 12 insertions(+), 14 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -102,26 +102,31 @@
+ opp-microvolt = <725000>;
+ clock-latency-ns = <200000>;
+ };
++
+ opp-1056000000 {
+ opp-hz = /bits/ 64 <1056000000>;
+ opp-microvolt = <787500>;
+ clock-latency-ns = <200000>;
+ };
++
+ opp-1320000000 {
+ opp-hz = /bits/ 64 <1320000000>;
+ opp-microvolt = <862500>;
+ clock-latency-ns = <200000>;
+ };
++
+ opp-1440000000 {
+ opp-hz = /bits/ 64 <1440000000>;
+ opp-microvolt = <925000>;
+ clock-latency-ns = <200000>;
+ };
++
+ opp-1608000000 {
+ opp-hz = /bits/ 64 <1608000000>;
+ opp-microvolt = <987500>;
+ clock-latency-ns = <200000>;
+ };
++
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+ opp-microvolt = <1062500>;
+@@ -131,8 +136,7 @@
+
+ pmuv8: pmu {
+ compatible = "arm,cortex-a53-pmu";
+- interrupts = <GIC_PPI 7 (GIC_CPU_MASK_SIMPLE(4) |
+- IRQ_TYPE_LEVEL_HIGH)>;
++ interrupts = <GIC_PPI 7 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
+ };
+
+ psci: psci {
+@@ -734,24 +738,18 @@
+ phys = <&pcie_phy0>;
+ phy-names = "pciephy";
+
+- ranges = <0x81000000 0 0x20200000 0 0x20200000
+- 0 0x10000>, /* downstream I/O */
+- <0x82000000 0 0x20220000 0 0x20220000
+- 0 0xfde0000>; /* non-prefetchable memory */
++ ranges = <0x81000000 0 0x20200000 0 0x20200000 0 0x10000>,
++ <0x82000000 0 0x20220000 0 0x20220000 0 0xfde0000>;
+
+ interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "msi";
+
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 0x7>;
+- interrupt-map = <0 0 0 1 &intc 0 75
+- IRQ_TYPE_LEVEL_HIGH>, /* int_a */
+- <0 0 0 2 &intc 0 78
+- IRQ_TYPE_LEVEL_HIGH>, /* int_b */
+- <0 0 0 3 &intc 0 79
+- IRQ_TYPE_LEVEL_HIGH>, /* int_c */
+- <0 0 0 4 &intc 0 83
+- IRQ_TYPE_LEVEL_HIGH>; /* int_d */
++ interrupt-map = <0 0 0 1 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
++ <0 0 0 2 &intc 0 78 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
++ <0 0 0 3 &intc 0 79 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
++ <0 0 0 4 &intc 0 83 IRQ_TYPE_LEVEL_HIGH>; /* int_d */
+
+ clocks = <&gcc GCC_SYS_NOC_PCIE0_AXI_CLK>,
+ <&gcc GCC_PCIE0_AXI_M_CLK>,
diff --git a/target/linux/qualcommax/patches-6.1/0036-v6.3-arm64-dts-qcom-ipq6018-Use-lowercase-hex.patch b/target/linux/qualcommax/patches-6.1/0036-v6.3-arm64-dts-qcom-ipq6018-Use-lowercase-hex.patch
new file mode 100644
index 0000000000..35aa46bb10
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0036-v6.3-arm64-dts-qcom-ipq6018-Use-lowercase-hex.patch
@@ -0,0 +1,25 @@
+From 7356ae3e10abd1d71f06ff0b8a8e72aa7c955c57 Mon Sep 17 00:00:00 2001
+From: Konrad Dybcio <konrad.dybcio@linaro.org>
+Date: Mon, 2 Jan 2023 10:46:30 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq6018: Use lowercase hex
+
+One value escaped my previous lowercase hexification. Take care of it.
+
+Signed-off-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230102094642.74254-6-konrad.dybcio@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -381,7 +381,7 @@
+
+ usb2: usb@70f8800 {
+ compatible = "qcom,ipq6018-dwc3", "qcom,dwc3";
+- reg = <0x0 0x070F8800 0x0 0x400>;
++ reg = <0x0 0x070f8800 0x0 0x400>;
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
diff --git a/target/linux/qualcommax/patches-6.1/0037-v6.3-arm64-dts-qcom-ipq6018-align-RPM-G-Link-node-with.patch b/target/linux/qualcommax/patches-6.1/0037-v6.3-arm64-dts-qcom-ipq6018-align-RPM-G-Link-node-with.patch
new file mode 100644
index 0000000000..c939d126bf
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0037-v6.3-arm64-dts-qcom-ipq6018-align-RPM-G-Link-node-with.patch
@@ -0,0 +1,28 @@
+From 679ee73bbee28cab441008f8cca38160cc8f3d05 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Wed, 8 Feb 2023 11:15:39 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq6018: align RPM G-Link node with
+ bindings
+
+Bindings expect (and most of DTS use) the RPM G-Link node name to be
+"rpm-requests".
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230208101545.45711-1-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -176,7 +176,7 @@
+ qcom,rpm-msg-ram = <&rpm_msg_ram>;
+ mboxes = <&apcs_glb 0>;
+
+- rpm_requests: glink-channel {
++ rpm_requests: rpm-requests {
+ compatible = "qcom,rpm-ipq6018";
+ qcom,glink-channels = "rpm_requests";
+
diff --git a/target/linux/qualcommax/patches-6.1/0038-v6.4-arm64-dts-qcom-ipq6018-cp01-c1-drop-SPI-cs-select.patch b/target/linux/qualcommax/patches-6.1/0038-v6.4-arm64-dts-qcom-ipq6018-cp01-c1-drop-SPI-cs-select.patch
new file mode 100644
index 0000000000..28688069b8
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0038-v6.4-arm64-dts-qcom-ipq6018-cp01-c1-drop-SPI-cs-select.patch
@@ -0,0 +1,27 @@
+From afa8eb675fc6dd606783ed2350de90927d6fb9d3 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Wed, 8 Mar 2023 13:59:01 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq6018-cp01-c1: drop SPI cs-select
+
+The SPI controller nodes do not use/allow cs-select property:
+
+ ipq6018-cp01-c1.dtb: spi@78b5000: Unevaluated properties are not allowed ('cs-select' was unexpected)
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230308125906.236885-6-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dts | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dts
++++ b/arch/arm64/boot/dts/qcom/ipq6018-cp01-c1.dts
+@@ -36,7 +36,6 @@
+ };
+
+ &blsp1_spi1 {
+- cs-select = <0>;
+ pinctrl-0 = <&spi_0_pins>;
+ pinctrl-names = "default";
+ status = "okay";
diff --git a/target/linux/qualcommax/patches-6.1/0039-v6.5-arm64-dts-qcom-add-few-more-reserved-memory-region.patch b/target/linux/qualcommax/patches-6.1/0039-v6.5-arm64-dts-qcom-add-few-more-reserved-memory-region.patch
new file mode 100644
index 0000000000..d1280b528b
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0039-v6.5-arm64-dts-qcom-add-few-more-reserved-memory-region.patch
@@ -0,0 +1,92 @@
+From 0cd4e90cb2dec02ff859f5c98f744f43a23aea65 Mon Sep 17 00:00:00 2001
+From: Vignesh Viswanathan <quic_viswanat@quicinc.com>
+Date: Fri, 26 May 2023 16:36:53 +0530
+Subject: [PATCH] arm64: dts: qcom: add few more reserved memory region
+
+In IPQ SoCs, bootloader will collect the system RAM contents upon crash
+for the post morterm analysis. If we don't reserve the memory region used
+by bootloader, obviously linux will consume it and upon next boot on
+crash, bootloader will be loaded in the same region, which will lead to
+loose some of the data, sometimes we may miss out critical information.
+So lets reserve the region used by the bootloader.
+
+Similarly SBL copies some data into the reserved region and it will be
+used in the crash scenario. So reserve 1MB for SBL as well.
+
+While at it, drop the size padding in the reserved memory region,
+wherever applicable.
+
+Signed-off-by: Vignesh Viswanathan <quic_viswanat@quicinc.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526110653.27777-4-quic_viswanat@quicinc.com
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 16 +++++++++++++---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 14 ++++++++++++--
+ 2 files changed, 25 insertions(+), 5 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -154,18 +154,28 @@
+ no-map;
+ };
+
++ bootloader@4a100000 {
++ reg = <0x0 0x4a100000 0x0 0x400000>;
++ no-map;
++ };
++
++ sbl@4a500000 {
++ reg = <0x0 0x4a500000 0x0 0x100000>;
++ no-map;
++ };
++
+ tz: memory@4a600000 {
+- reg = <0x0 0x4a600000 0x0 0x00400000>;
++ reg = <0x0 0x4a600000 0x0 0x400000>;
+ no-map;
+ };
+
+ smem_region: memory@4aa00000 {
+- reg = <0x0 0x4aa00000 0x0 0x00100000>;
++ reg = <0x0 0x4aa00000 0x0 0x100000>;
+ no-map;
+ };
+
+ q6_region: memory@4ab00000 {
+- reg = <0x0 0x4ab00000 0x0 0x05500000>;
++ reg = <0x0 0x4ab00000 0x0 0x5500000>;
+ no-map;
+ };
+ };
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -85,17 +85,27 @@
+ #size-cells = <2>;
+ ranges;
+
++ bootloader@4a600000 {
++ reg = <0x0 0x4a600000 0x0 0x400000>;
++ no-map;
++ };
++
++ sbl@4aa00000 {
++ reg = <0x0 0x4aa00000 0x0 0x100000>;
++ no-map;
++ };
++
+ smem@4ab00000 {
+ compatible = "qcom,smem";
+- reg = <0x0 0x4ab00000 0x0 0x00100000>;
++ reg = <0x0 0x4ab00000 0x0 0x100000>;
+ no-map;
+
+ hwlocks = <&tcsr_mutex 3>;
+ };
+
+ memory@4ac00000 {
++ reg = <0x0 0x4ac00000 0x0 0x400000>;
+ no-map;
+- reg = <0x0 0x4ac00000 0x0 0x00400000>;
+ };
+ };
+
diff --git a/target/linux/qualcommax/patches-6.1/0040-v6.5-arm64-dts-qcom-enable-the-download-mode-support.patch b/target/linux/qualcommax/patches-6.1/0040-v6.5-arm64-dts-qcom-enable-the-download-mode-support.patch
new file mode 100644
index 0000000000..6dd185f6e0
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0040-v6.5-arm64-dts-qcom-enable-the-download-mode-support.patch
@@ -0,0 +1,49 @@
+From 9b2406aaba7841863ac041225316c1ec1c86ea36 Mon Sep 17 00:00:00 2001
+From: Vignesh Viswanathan <quic_viswanat@quicinc.com>
+Date: Fri, 26 May 2023 16:36:52 +0530
+Subject: [PATCH] arm64: dts: qcom: enable the download mode support
+
+Like any other Qualcomm SoCs, IPQ8074 and IPQ6018 also supports the
+download mode to collect the RAM dumps if system crashes, to perform
+the post mortem analysis. Add support for the same.
+
+Signed-off-by: Vignesh Viswanathan <quic_viswanat@quicinc.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526110653.27777-3-quic_viswanat@quicinc.com
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 1 +
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 6 ++++++
+ 2 files changed, 7 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -90,6 +90,7 @@
+ firmware {
+ scm {
+ compatible = "qcom,scm-ipq6018", "qcom,scm";
++ qcom,dload-mode = <&tcsr 0x6100>;
+ };
+ };
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -112,6 +112,7 @@
+ firmware {
+ scm {
+ compatible = "qcom,scm-ipq8074", "qcom,scm";
++ qcom,dload-mode = <&tcsr 0x6100>;
+ };
+ };
+
+@@ -386,6 +387,11 @@
+ #hwlock-cells = <1>;
+ };
+
++ tcsr: syscon@1937000 {
++ compatible = "qcom,tcsr-ipq8074", "syscon";
++ reg = <0x01937000 0x21000>;
++ };
++
+ spmi_bus: spmi@200f000 {
+ compatible = "qcom,spmi-pmic-arb";
+ reg = <0x0200f000 0x001000>,
diff --git a/target/linux/qualcommax/patches-6.1/0041-v6.5-arm64-dts-qcom-ipq6018-correct-qrng-unit-address.patch b/target/linux/qualcommax/patches-6.1/0041-v6.5-arm64-dts-qcom-ipq6018-correct-qrng-unit-address.patch
new file mode 100644
index 0000000000..e9b92b596e
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0041-v6.5-arm64-dts-qcom-ipq6018-correct-qrng-unit-address.patch
@@ -0,0 +1,29 @@
+From 085058786a7890dd44ec623fe5ac74db870f6b93 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Wed, 19 Apr 2023 23:18:39 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: correct qrng unit address
+
+Match unit-address to reg entry to fix dtbs W=1 warnings:
+
+ Warning (simple_bus_reg): /soc/qrng@e1000: simple-bus unit address format error, expected "e3000"
+
+Fixes: 5bf635621245 ("arm64: dts: ipq6018: Add a few device nodes")
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230419211856.79332-1-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -312,7 +312,7 @@
+ status = "disabled";
+ };
+
+- prng: qrng@e1000 {
++ prng: qrng@e3000 {
+ compatible = "qcom,prng-ee";
+ reg = <0x0 0x000e3000 0x0 0x1000>;
+ clocks = <&gcc GCC_PRNG_AHB_CLK>;
diff --git a/target/linux/qualcommax/patches-6.1/0042-v6.5-arm64-dts-qcom-ipq6018-add-unit-address-to-soc-node.patch b/target/linux/qualcommax/patches-6.1/0042-v6.5-arm64-dts-qcom-ipq6018-add-unit-address-to-soc-node.patch
new file mode 100644
index 0000000000..821c9890c6
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0042-v6.5-arm64-dts-qcom-ipq6018-add-unit-address-to-soc-node.patch
@@ -0,0 +1,28 @@
+From 393595d4ffbd0a1fafd5548f8de1b8487a037cf2 Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Thu, 20 Apr 2023 08:36:04 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: add unit address to soc node
+
+"soc" node is supposed to have unit address:
+
+ Warning (unit_address_vs_reg): /soc: node has a reg or ranges property, but no unit name
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230420063610.11068-1-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -209,7 +209,7 @@
+ hwlocks = <&tcsr_mutex 3>;
+ };
+
+- soc: soc {
++ soc: soc@0 {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges = <0 0 0 0 0x0 0xffffffff>;
diff --git a/target/linux/qualcommax/patches-6.1/0043-v6.5-arm64-dts-qcom-ipq6018-add-QFPROM-node.patch b/target/linux/qualcommax/patches-6.1/0043-v6.5-arm64-dts-qcom-ipq6018-add-QFPROM-node.patch
new file mode 100644
index 0000000000..68895c5c96
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0043-v6.5-arm64-dts-qcom-ipq6018-add-QFPROM-node.patch
@@ -0,0 +1,34 @@
+From 546f0617a22a481f3ca1f7e058aea0c40517c64e Mon Sep 17 00:00:00 2001
+From: Kathiravan T <quic_kathirav@quicinc.com>
+Date: Fri, 26 May 2023 18:23:04 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq6018: add QFPROM node
+
+IPQ6018 has efuse region to determine the various HW quirks. Lets
+add the initial support and the individual fuses will be added as they
+are required.
+
+Signed-off-by: Kathiravan T <quic_kathirav@quicinc.com>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526125305.19626-4-quic_kathirav@quicinc.com
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -312,6 +312,13 @@
+ status = "disabled";
+ };
+
++ qfprom: efuse@a4000 {
++ compatible = "qcom,ipq6018-qfprom", "qcom,qfprom";
++ reg = <0x0 0x000a4000 0x0 0x2000>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++ };
++
+ prng: qrng@e3000 {
+ compatible = "qcom,prng-ee";
+ reg = <0x0 0x000e3000 0x0 0x1000>;
diff --git a/target/linux/qualcommax/patches-6.1/0044-v6.5-arm64-dts-qcom-ipq6018-drop-incorrect-SPI-bus.patch b/target/linux/qualcommax/patches-6.1/0044-v6.5-arm64-dts-qcom-ipq6018-drop-incorrect-SPI-bus.patch
new file mode 100644
index 0000000000..b2057e588e
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0044-v6.5-arm64-dts-qcom-ipq6018-drop-incorrect-SPI-bus.patch
@@ -0,0 +1,37 @@
+From b8420d478aa3fc739fcdba6b4b945850b356cb3b Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Sun, 16 Apr 2023 14:37:25 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: drop incorrect SPI bus
+ spi-max-frequency
+
+The spi-max-frequency property belongs to SPI devices, not SPI
+controller:
+
+ ipq6018-cp01-c1.dtb: spi@78b5000: Unevaluated properties are not allowed ('spi-max-frequency' was unexpected)
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230416123730.300863-1-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -458,7 +458,6 @@
+ #size-cells = <0>;
+ reg = <0x0 0x078b5000 0x0 0x600>;
+ interrupts = <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>;
+- spi-max-frequency = <50000000>;
+ clocks = <&gcc GCC_BLSP1_QUP1_SPI_APPS_CLK>,
+ <&gcc GCC_BLSP1_AHB_CLK>;
+ clock-names = "core", "iface";
+@@ -473,7 +472,6 @@
+ #size-cells = <0>;
+ reg = <0x0 0x078b6000 0x0 0x600>;
+ interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
+- spi-max-frequency = <50000000>;
+ clocks = <&gcc GCC_BLSP1_QUP2_SPI_APPS_CLK>,
+ <&gcc GCC_BLSP1_AHB_CLK>;
+ clock-names = "core", "iface";
diff --git a/target/linux/qualcommax/patches-6.1/0045-v6.5-arm64-dts-qcom-ipq8074-drop-incorrect-SPI-bus.patch b/target/linux/qualcommax/patches-6.1/0045-v6.5-arm64-dts-qcom-ipq8074-drop-incorrect-SPI-bus.patch
new file mode 100644
index 0000000000..52ba16cc9f
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0045-v6.5-arm64-dts-qcom-ipq8074-drop-incorrect-SPI-bus.patch
@@ -0,0 +1,29 @@
+From e6e0e706940b64e3a77e0a4840037692f109bd5f Mon Sep 17 00:00:00 2001
+From: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Date: Sun, 16 Apr 2023 14:37:26 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074: drop incorrect SPI bus
+ spi-max-frequency
+
+The spi-max-frequency property belongs to SPI devices, not SPI
+controller:
+
+ ipq8074-hk01.dtb: spi@78b5000: Unevaluated properties are not allowed ('spi-max-frequency' was unexpected)
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230416123730.300863-2-krzysztof.kozlowski@linaro.org
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -487,7 +487,6 @@
+ #size-cells = <0>;
+ reg = <0x078b5000 0x600>;
+ interrupts = <GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>;
+- spi-max-frequency = <50000000>;
+ clocks = <&gcc GCC_BLSP1_QUP1_SPI_APPS_CLK>,
+ <&gcc GCC_BLSP1_AHB_CLK>;
+ clock-names = "core", "iface";
diff --git a/target/linux/qualcommax/patches-6.1/0046-v6.6-clk-qcom-gcc-ipq6018-Use-floor-ops-for-sdcc-clocks.patch b/target/linux/qualcommax/patches-6.1/0046-v6.6-clk-qcom-gcc-ipq6018-Use-floor-ops-for-sdcc-clocks.patch
new file mode 100644
index 0000000000..9ae72730d8
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0046-v6.6-clk-qcom-gcc-ipq6018-Use-floor-ops-for-sdcc-clocks.patch
@@ -0,0 +1,27 @@
+From 56e5ae0116aef87273cf1812d608645b076e4f02 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 25 Apr 2023 12:11:49 +0300
+Subject: [PATCH] clk: qcom: gcc-ipq6018: Use floor ops for sdcc clocks
+
+SDCC clocks must be rounded down to avoid overclocking the controller.
+
+Fixes: d9db07f088af ("clk: qcom: Add ipq6018 Global Clock Controller support")
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/1682413909-24927-1-git-send-email-mantas@8devices.com
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -1702,7 +1702,7 @@ static struct clk_rcg2 usb0_mock_utmi_cl
+ .name = "usb0_mock_utmi_clk_src",
+ .parent_data = gcc_xo_gpll6_gpll0_gpll0_out_main_div2,
+ .num_parents = 4,
+- .ops = &clk_rcg2_ops,
++ .ops = &clk_rcg2_floor_ops,
+ },
+ };
+
diff --git a/target/linux/qualcommax/patches-6.1/0047-v6.6-clk-qcom-gcc-ipq6018-drop-redundant-F-define.patch b/target/linux/qualcommax/patches-6.1/0047-v6.6-clk-qcom-gcc-ipq6018-drop-redundant-F-define.patch
new file mode 100644
index 0000000000..19dc9482e6
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0047-v6.6-clk-qcom-gcc-ipq6018-drop-redundant-F-define.patch
@@ -0,0 +1,27 @@
+From 923f7d678b2ae3d522543058514d5605c185633b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 19:44:07 +0200
+Subject: [PATCH] clk: qcom: gcc-ipq6018: drop redundant F define
+
+The same exact F frequency table entry is defined in clk-rcg.h
+Drop the redundant define to cleanup code.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230417174408.23722-1-ansuelsmth@gmail.com
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -26,8 +26,6 @@
+ #include "clk-regmap-mux.h"
+ #include "reset.h"
+
+-#define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) }
+-
+ enum {
+ P_XO,
+ P_BIAS_PLL,
diff --git a/target/linux/qualcommax/patches-6.1/0048-v6.6-clk-qcom-gcc-ipq6018-update-UBI32-PLL.patch b/target/linux/qualcommax/patches-6.1/0048-v6.6-clk-qcom-gcc-ipq6018-update-UBI32-PLL.patch
new file mode 100644
index 0000000000..e38b40278c
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0048-v6.6-clk-qcom-gcc-ipq6018-update-UBI32-PLL.patch
@@ -0,0 +1,39 @@
+From f4f0c8acee0e41c5fbae7a7ad06087668ddce0d6 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 26 May 2023 21:08:54 +0200
+Subject: [PATCH] clk: qcom: gcc-ipq6018: update UBI32 PLL
+
+Update the UBI32 alpha PLL config to the latest values from the downstream
+QCA 5.4 kernel.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230526190855.2941291-1-robimarko@gmail.com
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -4143,15 +4143,20 @@ static struct clk_branch gcc_dcc_clk = {
+
+ static const struct alpha_pll_config ubi32_pll_config = {
+ .l = 0x3e,
+- .alpha = 0x57,
++ .alpha = 0x6667,
+ .config_ctl_val = 0x240d6aa8,
+ .config_ctl_hi_val = 0x3c2,
++ .config_ctl_val = 0x240d4828,
++ .config_ctl_hi_val = 0x6,
+ .main_output_mask = BIT(0),
+ .aux_output_mask = BIT(1),
+ .pre_div_val = 0x0,
+ .pre_div_mask = BIT(12),
+ .post_div_val = 0x0,
+ .post_div_mask = GENMASK(9, 8),
++ .alpha_en_mask = BIT(24),
++ .test_ctl_val = 0x1C0000C0,
++ .test_ctl_hi_val = 0x4000,
+ };
+
+ static const struct alpha_pll_config nss_crypto_pll_config = {
diff --git a/target/linux/qualcommax/patches-6.1/0049-v6.6-clk-qcom-gcc-ipq6018-remove-duplicate-initializers.patch b/target/linux/qualcommax/patches-6.1/0049-v6.6-clk-qcom-gcc-ipq6018-remove-duplicate-initializers.patch
new file mode 100644
index 0000000000..e4faac1b6e
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0049-v6.6-clk-qcom-gcc-ipq6018-remove-duplicate-initializers.patch
@@ -0,0 +1,38 @@
+From 5ae7899765607e97e5eb34486336898c8d9ec654 Mon Sep 17 00:00:00 2001
+From: Arnd Bergmann <arnd@arndb.de>
+Date: Thu, 1 Jun 2023 23:34:12 +0200
+Subject: [PATCH] clk: qcom: gcc-ipq6018: remove duplicate initializers
+
+A recent change added new initializers for .config_ctl_val and
+.config_ctl_hi_val but left the old values in place:
+
+drivers/clk/qcom/gcc-ipq6018.c:4155:27: error: initialized field overwritten [-Werror=override-init]
+ 4155 | .config_ctl_val = 0x240d4828,
+ | ^~~~~~~~~~
+drivers/clk/qcom/gcc-ipq6018.c:4156:30: error: initialized field overwritten [-Werror=override-init]
+ 4156 | .config_ctl_hi_val = 0x6,
+ | ^~~
+
+Remove the unused ones now to avoid confusion.
+
+Fixes: f4f0c8acee0e4 ("clk: qcom: gcc-ipq6018: update UBI32 PLL")
+Signed-off-by: Arnd Bergmann <arnd@arndb.de>
+Reviewed-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Link: https://lore.kernel.org/r/20230601213416.3373599-1-arnd@kernel.org
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -4144,8 +4144,6 @@ static struct clk_branch gcc_dcc_clk = {
+ static const struct alpha_pll_config ubi32_pll_config = {
+ .l = 0x3e,
+ .alpha = 0x6667,
+- .config_ctl_val = 0x240d6aa8,
+- .config_ctl_hi_val = 0x3c2,
+ .config_ctl_val = 0x240d4828,
+ .config_ctl_hi_val = 0x6,
+ .main_output_mask = BIT(0),
diff --git a/target/linux/qualcommax/patches-6.1/0050-v6.6-soc-qcom-Add-RPM-processor-subsystem-driver.patch b/target/linux/qualcommax/patches-6.1/0050-v6.6-soc-qcom-Add-RPM-processor-subsystem-driver.patch
new file mode 100644
index 0000000000..c2c26fb079
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0050-v6.6-soc-qcom-Add-RPM-processor-subsystem-driver.patch
@@ -0,0 +1,132 @@
+From 8ddfa81d090c71fd6cb3cb8ca1d420c0da33a575 Mon Sep 17 00:00:00 2001
+From: Stephan Gerhold <stephan@gerhold.net>
+Date: Thu, 15 Jun 2023 18:50:42 +0200
+Subject: [PATCH] soc: qcom: Add RPM processor/subsystem driver
+
+Add a simple driver for the qcom,rpm-proc compatible that registers the
+"smd-edge" and populates other children defined in the device tree.
+
+Note that the DT schema belongs to the remoteproc subsystem while this
+driver is added inside soc/qcom. I argue that the RPM *is* a remoteproc,
+but as an implementation detail in Linux it can currently not benefit
+from anything provided by the remoteproc subsystem. The RPM firmware is
+usually already loaded and started by earlier components in the boot
+chain and is not meant to be ever restarted.
+
+To avoid breaking existing kernel configurations the driver is always
+built when smd-rpm.c is also built. They belong closely together anyway.
+To avoid build errors CONFIG_RPMSG_QCOM_SMD must be also built-in if
+rpm-proc is.
+
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
+Link: https://lore.kernel.org/r/20230531-rpm-rproc-v3-9-a07dcdefd918@gerhold.net
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ drivers/soc/qcom/Kconfig | 1 +
+ drivers/soc/qcom/Makefile | 2 +-
+ drivers/soc/qcom/rpm-proc.c | 77 +++++++++++++++++++++++++++++++++++++
+ 3 files changed, 79 insertions(+), 1 deletion(-)
+ create mode 100644 drivers/soc/qcom/rpm-proc.c
+
+--- a/drivers/soc/qcom/Kconfig
++++ b/drivers/soc/qcom/Kconfig
+@@ -153,6 +153,7 @@ config QCOM_SMD_RPM
+ tristate "Qualcomm Resource Power Manager (RPM) over SMD"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on RPMSG
++ depends on RPMSG_QCOM_SMD || RPMSG_QCOM_SMD=n
+ help
+ If you say yes to this option, support will be included for the
+ Resource Power Manager system found in the Qualcomm 8974 based
+--- a/drivers/soc/qcom/Makefile
++++ b/drivers/soc/qcom/Makefile
+@@ -14,7 +14,7 @@ obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_me
+ obj-$(CONFIG_QCOM_RPMH) += qcom_rpmh.o
+ qcom_rpmh-y += rpmh-rsc.o
+ qcom_rpmh-y += rpmh.o
+-obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
++obj-$(CONFIG_QCOM_SMD_RPM) += rpm-proc.o smd-rpm.o
+ obj-$(CONFIG_QCOM_SMEM) += smem.o
+ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
+ obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
+--- /dev/null
++++ b/drivers/soc/qcom/rpm-proc.c
+@@ -0,0 +1,77 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/* Copyright (c) 2021-2023, Stephan Gerhold <stephan@gerhold.net> */
++
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/of_platform.h>
++#include <linux/platform_device.h>
++#include <linux/rpmsg/qcom_smd.h>
++
++static int rpm_proc_probe(struct platform_device *pdev)
++{
++ struct qcom_smd_edge *edge = NULL;
++ struct device *dev = &pdev->dev;
++ struct device_node *edge_node;
++ int ret;
++
++ edge_node = of_get_child_by_name(dev->of_node, "smd-edge");
++ if (edge_node) {
++ edge = qcom_smd_register_edge(dev, edge_node);
++ of_node_put(edge_node);
++ if (IS_ERR(edge))
++ return dev_err_probe(dev, PTR_ERR(edge),
++ "Failed to register smd-edge\n");
++ }
++
++ ret = devm_of_platform_populate(dev);
++ if (ret) {
++ dev_err(dev, "Failed to populate child devices: %d\n", ret);
++ goto err;
++ }
++
++ platform_set_drvdata(pdev, edge);
++ return 0;
++err:
++ if (edge)
++ qcom_smd_unregister_edge(edge);
++ return ret;
++}
++
++static void rpm_proc_remove(struct platform_device *pdev)
++{
++ struct qcom_smd_edge *edge = platform_get_drvdata(pdev);
++
++ if (edge)
++ qcom_smd_unregister_edge(edge);
++}
++
++static const struct of_device_id rpm_proc_of_match[] = {
++ { .compatible = "qcom,rpm-proc", },
++ { /* sentinel */ }
++};
++MODULE_DEVICE_TABLE(of, rpm_proc_of_match);
++
++static struct platform_driver rpm_proc_driver = {
++ .probe = rpm_proc_probe,
++ .remove_new = rpm_proc_remove,
++ .driver = {
++ .name = "qcom-rpm-proc",
++ .of_match_table = rpm_proc_of_match,
++ },
++};
++
++static int __init rpm_proc_init(void)
++{
++ return platform_driver_register(&rpm_proc_driver);
++}
++arch_initcall(rpm_proc_init);
++
++static void __exit rpm_proc_exit(void)
++{
++ platform_driver_unregister(&rpm_proc_driver);
++}
++module_exit(rpm_proc_exit);
++
++MODULE_DESCRIPTION("Qualcomm RPM processor/subsystem driver");
++MODULE_AUTHOR("Stephan Gerhold <stephan@gerhold.net>");
++MODULE_LICENSE("GPL");
diff --git a/target/linux/qualcommax/patches-6.1/0051-v6.6-arm64-dts-qcom-Add-rpm-proc-node-for-GLINK.patch b/target/linux/qualcommax/patches-6.1/0051-v6.6-arm64-dts-qcom-Add-rpm-proc-node-for-GLINK.patch
new file mode 100644
index 0000000000..746a391b42
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0051-v6.6-arm64-dts-qcom-Add-rpm-proc-node-for-GLINK.patch
@@ -0,0 +1,93 @@
+From 7e1acc8b92a3b67db1e5255adae2851d58d74434 Mon Sep 17 00:00:00 2001
+From: Stephan Gerhold <stephan@gerhold.net>
+Date: Thu, 15 Jun 2023 18:50:44 +0200
+Subject: [PATCH] arm64: dts: qcom: Add rpm-proc node for GLINK gplatforms
+
+Rather than having the RPM GLINK channels as the only child of a dummy
+top-level rpm-glink node, switch to representing the RPM as remoteproc
+like all the other remoteprocs (modem DSP, ...).
+
+This allows assigning additional subdevices to it like the MPM
+interrupt-controller or rpm-master-stats.
+
+Tested-by: Konrad Dybcio <konrad.dybcio@linaro.org> # SM6375
+Signed-off-by: Stephan Gerhold <stephan@gerhold.net>
+Link: https://lore.kernel.org/r/20230531-rpm-rproc-v3-11-a07dcdefd918@gerhold.net
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 48 ++++----
+ arch/arm64/boot/dts/qcom/ipq9574.dtsi | 28 +++--
+ arch/arm64/boot/dts/qcom/msm8996.dtsi | 113 +++++++++----------
+ arch/arm64/boot/dts/qcom/msm8998.dtsi | 102 ++++++++---------
+ arch/arm64/boot/dts/qcom/qcm2290.dtsi | 126 ++++++++++-----------
+ arch/arm64/boot/dts/qcom/qcs404.dtsi | 152 +++++++++++++-------------
+ arch/arm64/boot/dts/qcom/sdm630.dtsi | 132 +++++++++++-----------
+ arch/arm64/boot/dts/qcom/sm6115.dtsi | 128 +++++++++++-----------
+ arch/arm64/boot/dts/qcom/sm6125.dtsi | 140 ++++++++++++------------
+ arch/arm64/boot/dts/qcom/sm6375.dtsi | 126 ++++++++++-----------
+ 10 files changed, 566 insertions(+), 529 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -145,6 +145,32 @@
+ method = "smc";
+ };
+
++ rpm: remoteproc {
++ compatible = "qcom,ipq6018-rpm-proc", "qcom,rpm-proc";
++
++ glink-edge {
++ compatible = "qcom,glink-rpm";
++ interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
++ qcom,rpm-msg-ram = <&rpm_msg_ram>;
++ mboxes = <&apcs_glb 0>;
++
++ rpm_requests: rpm-requests {
++ compatible = "qcom,rpm-ipq6018";
++ qcom,glink-channels = "rpm_requests";
++
++ regulators {
++ compatible = "qcom,rpm-mp5496-regulators";
++
++ ipq6018_s2: s2 {
++ regulator-min-microvolt = <725000>;
++ regulator-max-microvolt = <1062500>;
++ regulator-always-on;
++ };
++ };
++ };
++ };
++ };
++
+ reserved-memory {
+ #address-cells = <2>;
+ #size-cells = <2>;
+@@ -181,28 +207,6 @@
+ };
+ };
+
+- rpm-glink {
+- compatible = "qcom,glink-rpm";
+- interrupts = <GIC_SPI 168 IRQ_TYPE_EDGE_RISING>;
+- qcom,rpm-msg-ram = <&rpm_msg_ram>;
+- mboxes = <&apcs_glb 0>;
+-
+- rpm_requests: rpm-requests {
+- compatible = "qcom,rpm-ipq6018";
+- qcom,glink-channels = "rpm_requests";
+-
+- regulators {
+- compatible = "qcom,rpm-mp5496-regulators";
+-
+- ipq6018_s2: s2 {
+- regulator-min-microvolt = <725000>;
+- regulator-max-microvolt = <1062500>;
+- regulator-always-on;
+- };
+- };
+- };
+- };
+-
+ smem {
+ compatible = "qcom,smem";
+ memory-region = <&smem_region>;
diff --git a/target/linux/qualcommax/patches-6.1/0052-v6.7-arm64-dts-qcom-ipq6018-include-the-GPLL0-as.patch b/target/linux/qualcommax/patches-6.1/0052-v6.7-arm64-dts-qcom-ipq6018-include-the-GPLL0-as.patch
new file mode 100644
index 0000000000..b70d7bf69c
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0052-v6.7-arm64-dts-qcom-ipq6018-include-the-GPLL0-as.patch
@@ -0,0 +1,35 @@
+From 0133c7af3aa0420778d106cb90db708cfa45f2c6 Mon Sep 17 00:00:00 2001
+From: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+Date: Thu, 14 Sep 2023 12:29:59 +0530
+Subject: [PATCH] arm64: dts: qcom: ipq6018: include the GPLL0 as clock
+ provider for mailbox
+
+While the kernel is booting up, APSS clock / CPU clock will be running
+at 800MHz with GPLL0 as source. Once the cpufreq driver is available,
+APSS PLL will be configured to the rate based on the opp table and the
+source also will be changed to APSS_PLL_EARLY. So allow the mailbox to
+consume the GPLL0, with this inclusion, CPU Freq correctly reports that
+CPU is running at 800MHz rather than 24MHz.
+
+Signed-off-by: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Link: https://lore.kernel.org/r/20230913-gpll_cleanup-v2-9-c8ceb1a37680@quicinc.com
+[bjorn: Updated commit message, as requested by Kathiravan]
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -618,8 +618,8 @@
+ compatible = "qcom,ipq6018-apcs-apps-global";
+ reg = <0x0 0x0b111000 0x0 0x1000>;
+ #clock-cells = <1>;
+- clocks = <&a53pll>, <&xo>;
+- clock-names = "pll", "xo";
++ clocks = <&a53pll>, <&xo>, <&gcc GPLL0>;
++ clock-names = "pll", "xo", "gpll0";
+ #mbox-cells = <1>;
+ };
+
diff --git a/target/linux/qualcommax/patches-6.1/0053-v6.7-clk-qcom-gcc-ipq6018-add-QUP6-I2C-clock.patch b/target/linux/qualcommax/patches-6.1/0053-v6.7-clk-qcom-gcc-ipq6018-add-QUP6-I2C-clock.patch
new file mode 100644
index 0000000000..0858528933
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0053-v6.7-clk-qcom-gcc-ipq6018-add-QUP6-I2C-clock.patch
@@ -0,0 +1,57 @@
+From 3dcf7b59393812a5fbd83f8cd8d34b94afb4c4d1 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sat, 21 Oct 2023 13:55:18 +0200
+Subject: [PATCH] clk: qcom: gcc-ipq6018: add QUP6 I2C clock
+
+QUP6 I2C clock is listed in the dt bindings but it was never included in
+the GCC driver.
+So lets add support for it, it is marked as criticial as it is used by RPM
+to communicate to the external PMIC over I2C so this clock must not be
+disabled.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Kathiravan Thirumoorthy <quic_kathirav@quicinc.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Link: https://lore.kernel.org/r/20231021115545.229060-1-robimarko@gmail.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -2120,6 +2120,26 @@ static struct clk_branch gcc_blsp1_qup5_
+ },
+ };
+
++static struct clk_branch gcc_blsp1_qup6_i2c_apps_clk = {
++ .halt_reg = 0x07010,
++ .clkr = {
++ .enable_reg = 0x07010,
++ .enable_mask = BIT(0),
++ .hw.init = &(struct clk_init_data){
++ .name = "gcc_blsp1_qup6_i2c_apps_clk",
++ .parent_hws = (const struct clk_hw *[]){
++ &blsp1_qup6_i2c_apps_clk_src.clkr.hw },
++ .num_parents = 1,
++ /*
++ * RPM uses QUP6 I2C to communicate with the external
++ * PMIC so it must not be disabled.
++ */
++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
++ .ops = &clk_branch2_ops,
++ },
++ },
++};
++
+ static struct clk_branch gcc_blsp1_qup6_spi_apps_clk = {
+ .halt_reg = 0x0700c,
+ .clkr = {
+@@ -4276,6 +4296,7 @@ static struct clk_regmap *gcc_ipq6018_cl
+ [GCC_BLSP1_QUP4_SPI_APPS_CLK] = &gcc_blsp1_qup4_spi_apps_clk.clkr,
+ [GCC_BLSP1_QUP5_I2C_APPS_CLK] = &gcc_blsp1_qup5_i2c_apps_clk.clkr,
+ [GCC_BLSP1_QUP5_SPI_APPS_CLK] = &gcc_blsp1_qup5_spi_apps_clk.clkr,
++ [GCC_BLSP1_QUP6_I2C_APPS_CLK] = &gcc_blsp1_qup6_i2c_apps_clk.clkr,
+ [GCC_BLSP1_QUP6_SPI_APPS_CLK] = &gcc_blsp1_qup6_spi_apps_clk.clkr,
+ [GCC_BLSP1_UART1_APPS_CLK] = &gcc_blsp1_uart1_apps_clk.clkr,
+ [GCC_BLSP1_UART2_APPS_CLK] = &gcc_blsp1_uart2_apps_clk.clkr,
diff --git a/target/linux/qualcommax/patches-6.1/0054-v6.8-arm64-dts-qcom-ipq6018-use-CPUFreq-NVMEM.patch b/target/linux/qualcommax/patches-6.1/0054-v6.8-arm64-dts-qcom-ipq6018-use-CPUFreq-NVMEM.patch
new file mode 100644
index 0000000000..1369a90a8a
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0054-v6.8-arm64-dts-qcom-ipq6018-use-CPUFreq-NVMEM.patch
@@ -0,0 +1,85 @@
+From 83afcf14edb9217e58837eb119da96d734a4b3b1 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sat, 21 Oct 2023 14:00:07 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: use CPUFreq NVMEM
+
+IPQ6018 comes in multiple SKU-s and some of them dont support all of the
+OPP-s that are current set, so lets utilize CPUFreq NVMEM to allow only
+supported OPP-s based on the SoC dynamically.
+
+As an example, IPQ6018 is generaly rated at 1.8GHz but some silicon only
+goes up to 1.5GHz and is marked as such via an eFuse.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Link: https://lore.kernel.org/r/20231021120048.231239-1-robimarko@gmail.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 14 +++++++++++++-
+ 1 file changed, 13 insertions(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -95,42 +95,49 @@
+ };
+
+ cpu_opp_table: opp-table-cpu {
+- compatible = "operating-points-v2";
++ compatible = "operating-points-v2-kryo-cpu";
++ nvmem-cells = <&cpu_speed_bin>;
+ opp-shared;
+
+ opp-864000000 {
+ opp-hz = /bits/ 64 <864000000>;
+ opp-microvolt = <725000>;
++ opp-supported-hw = <0xf>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1056000000 {
+ opp-hz = /bits/ 64 <1056000000>;
+ opp-microvolt = <787500>;
++ opp-supported-hw = <0xf>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1320000000 {
+ opp-hz = /bits/ 64 <1320000000>;
+ opp-microvolt = <862500>;
++ opp-supported-hw = <0x3>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1440000000 {
+ opp-hz = /bits/ 64 <1440000000>;
+ opp-microvolt = <925000>;
++ opp-supported-hw = <0x3>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1608000000 {
+ opp-hz = /bits/ 64 <1608000000>;
+ opp-microvolt = <987500>;
++ opp-supported-hw = <0x1>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+ opp-microvolt = <1062500>;
++ opp-supported-hw = <0x1>;
+ clock-latency-ns = <200000>;
+ };
+ };
+@@ -321,6 +328,11 @@
+ reg = <0x0 0x000a4000 0x0 0x2000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
++
++ cpu_speed_bin: cpu-speed-bin@135 {
++ reg = <0x135 0x1>;
++ bits = <7 1>;
++ };
+ };
+
+ prng: qrng@e3000 {
diff --git a/target/linux/qualcommax/patches-6.1/0055-v6.8-arm64-dts-ipq6018-Add-remaining-QUP-UART-node.patch b/target/linux/qualcommax/patches-6.1/0055-v6.8-arm64-dts-ipq6018-Add-remaining-QUP-UART-node.patch
new file mode 100644
index 0000000000..ca3c896047
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0055-v6.8-arm64-dts-ipq6018-Add-remaining-QUP-UART-node.patch
@@ -0,0 +1,81 @@
+From e6c32770ef83f3e8cc057f3920b1c06aa9d1c9c2 Mon Sep 17 00:00:00 2001
+From: Chukun Pan <amadeus@jmu.edu.cn>
+Date: Sun, 3 Dec 2023 23:39:14 +0800
+Subject: [PATCH] arm64: dts: qcom: ipq6018: Add remaining QUP UART node
+
+Add node to support all the QUP UART node controller inside of IPQ6018.
+Some routers use these bus to connect Bluetooth chips.
+
+Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
+Link: https://lore.kernel.org/r/20231203153914.532654-1-amadeus@jmu.edu.cn
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 50 +++++++++++++++++++++++++++
+ 1 file changed, 50 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -458,6 +458,26 @@
+ qcom,ee = <0>;
+ };
+
++ blsp1_uart1: serial@78af000 {
++ compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
++ reg = <0x0 0x78af000 0x0 0x200>;
++ interrupts = <GIC_SPI 107 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_BLSP1_UART1_APPS_CLK>,
++ <&gcc GCC_BLSP1_AHB_CLK>;
++ clock-names = "core", "iface";
++ status = "disabled";
++ };
++
++ blsp1_uart2: serial@78b0000 {
++ compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
++ reg = <0x0 0x78b0000 0x0 0x200>;
++ interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_BLSP1_UART2_APPS_CLK>,
++ <&gcc GCC_BLSP1_AHB_CLK>;
++ clock-names = "core", "iface";
++ status = "disabled";
++ };
++
+ blsp1_uart3: serial@78b1000 {
+ compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
+ reg = <0x0 0x078b1000 0x0 0x200>;
+@@ -466,6 +486,36 @@
+ <&gcc GCC_BLSP1_AHB_CLK>;
+ clock-names = "core", "iface";
+ status = "disabled";
++ };
++
++ blsp1_uart4: serial@78b2000 {
++ compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
++ reg = <0x0 0x078b2000 0x0 0x200>;
++ interrupts = <GIC_SPI 307 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_BLSP1_UART4_APPS_CLK>,
++ <&gcc GCC_BLSP1_AHB_CLK>;
++ clock-names = "core", "iface";
++ status = "disabled";
++ };
++
++ blsp1_uart5: serial@78b3000 {
++ compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
++ reg = <0x0 0x78b3000 0x0 0x200>;
++ interrupts = <GIC_SPI 308 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_BLSP1_UART5_APPS_CLK>,
++ <&gcc GCC_BLSP1_AHB_CLK>;
++ clock-names = "core", "iface";
++ status = "disabled";
++ };
++
++ blsp1_uart6: serial@78b4000 {
++ compatible = "qcom,msm-uartdm-v1.4", "qcom,msm-uartdm";
++ reg = <0x0 0x078b4000 0x0 0x200>;
++ interrupts = <GIC_SPI 309 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_BLSP1_UART6_APPS_CLK>,
++ <&gcc GCC_BLSP1_AHB_CLK>;
++ clock-names = "core", "iface";
++ status = "disabled";
+ };
+
+ blsp1_spi1: spi@78b5000 {
diff --git a/target/linux/qualcommax/patches-6.1/0056-v6.9-arm64-dts-qcom-Fix-hs_phy_irq-for-QUSB2-targets.patch b/target/linux/qualcommax/patches-6.1/0056-v6.9-arm64-dts-qcom-Fix-hs_phy_irq-for-QUSB2-targets.patch
new file mode 100644
index 0000000000..0f1b3e6a38
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0056-v6.9-arm64-dts-qcom-Fix-hs_phy_irq-for-QUSB2-targets.patch
@@ -0,0 +1,95 @@
+From 2c6597c72e9722ac020102d5af40126df0437b82 Mon Sep 17 00:00:00 2001
+From: Krishna Kurapati <quic_kriskura@quicinc.com>
+Date: Fri, 26 Jan 2024 00:29:18 +0530
+Subject: [PATCH] arm64: dts: qcom: Fix hs_phy_irq for QUSB2 targets
+
+On several QUSB2 Targets, the hs_phy_irq mentioned is actually
+qusb2_phy interrupt specific to QUSB2 PHY's. Rename hs_phy_irq
+to qusb2_phy for such targets.
+
+In actuality, the hs_phy_irq is also present in these targets, but
+kept in for debug purposes in hw test environments. This is not
+triggered by default and its functionality is mutually exclusive
+to that of qusb2_phy interrupt.
+
+Add missing hs_phy_irq's, pwr_event irq's for QUSB2 PHY targets.
+Add missing ss_phy_irq on some targets which allows for remote
+wakeup to work on a Super Speed link.
+
+Also modify order of interrupts in accordance to bindings update.
+Since driver looks up for interrupts by name and not by index, it
+is safe to modify order of these interrupts in the DT.
+
+Signed-off-by: Krishna Kurapati <quic_kriskura@quicinc.com>
+Link: https://lore.kernel.org/r/20240125185921.5062-2-quic_kriskura@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 13 +++++++++++++
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 14 ++++++++++++++
+ arch/arm64/boot/dts/qcom/msm8953.dtsi | 7 +++++--
+ arch/arm64/boot/dts/qcom/msm8996.dtsi | 8 ++++++--
+ arch/arm64/boot/dts/qcom/msm8998.dtsi | 7 +++++--
+ arch/arm64/boot/dts/qcom/sdm630.dtsi | 17 +++++++++++++----
+ arch/arm64/boot/dts/qcom/sm6115.dtsi | 9 +++++++--
+ arch/arm64/boot/dts/qcom/sm6125.dtsi | 9 +++++++--
+ 8 files changed, 70 insertions(+), 14 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -430,6 +430,12 @@
+ <&gcc GCC_USB1_MOCK_UTMI_CLK>;
+ assigned-clock-rates = <133330000>,
+ <24000000>;
++
++ interrupts = <GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "pwr_event",
++ "qusb2_phy";
++
+ resets = <&gcc GCC_USB1_BCR>;
+ status = "disabled";
+
+@@ -628,6 +634,13 @@
+ <133330000>,
+ <24000000>;
+
++ interrupts = <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 136 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "pwr_event",
++ "qusb2_phy",
++ "ss_phy_irq";
++
+ resets = <&gcc GCC_USB0_BCR>;
+ status = "disabled";
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -611,6 +611,13 @@
+ <133330000>,
+ <19200000>;
+
++ interrupts = <GIC_SPI 134 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 130 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "pwr_event",
++ "qusb2_phy",
++ "ss_phy_irq";
++
+ power-domains = <&gcc USB0_GDSC>;
+
+ resets = <&gcc GCC_USB0_BCR>;
+@@ -653,6 +660,13 @@
+ <133330000>,
+ <19200000>;
+
++ interrupts = <GIC_SPI 128 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 136 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 225 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "pwr_event",
++ "qusb2_phy",
++ "ss_phy_irq";
++
+ power-domains = <&gcc USB1_GDSC>;
+
+ resets = <&gcc GCC_USB1_BCR>;
diff --git a/target/linux/qualcommax/patches-6.1/0057-v6.8-hwspinlock-qcom-Remove-IPQ6018-SOC-specific-.patch b/target/linux/qualcommax/patches-6.1/0057-v6.8-hwspinlock-qcom-Remove-IPQ6018-SOC-specific-.patch
new file mode 100644
index 0000000000..25d56870a4
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0057-v6.8-hwspinlock-qcom-Remove-IPQ6018-SOC-specific-.patch
@@ -0,0 +1,32 @@
+From c3dc3d079d191c9149496b3c7fe1ece909386d93 Mon Sep 17 00:00:00 2001
+From: Vignesh Viswanathan <quic_viswanat@quicinc.com>
+Date: Tue, 5 Sep 2023 15:25:35 +0530
+Subject: [PATCH] hwspinlock: qcom: Remove IPQ6018 SOC specific compatible
+
+IPQ6018 has 32 tcsr_mutex hwlock registers with stride 0x1000.
+The compatible string qcom,ipq6018-tcsr-mutex is mapped to
+of_msm8226_tcsr_mutex which has 32 locks configured with stride of 0x80
+and doesn't match the HW present in IPQ6018.
+
+Remove IPQ6018 specific compatible string so that it fallsback to
+of_tcsr_mutex data which maps to the correct configuration for IPQ6018.
+
+Fixes: 5d4753f741d8 ("hwspinlock: qcom: add support for MMIO on older SoCs")
+Signed-off-by: Vignesh Viswanathan <quic_viswanat@quicinc.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@linaro.org>
+Link: https://lore.kernel.org/r/20230905095535.1263113-3-quic_viswanat@quicinc.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ drivers/hwspinlock/qcom_hwspinlock.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/hwspinlock/qcom_hwspinlock.c
++++ b/drivers/hwspinlock/qcom_hwspinlock.c
+@@ -115,7 +115,6 @@ static const struct of_device_id qcom_hw
+ { .compatible = "qcom,sfpb-mutex", .data = &of_sfpb_mutex },
+ { .compatible = "qcom,tcsr-mutex", .data = &of_tcsr_mutex },
+ { .compatible = "qcom,apq8084-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },
+- { .compatible = "qcom,ipq6018-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },
+ { .compatible = "qcom,msm8226-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },
+ { .compatible = "qcom,msm8974-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },
+ { .compatible = "qcom,msm8994-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },
diff --git a/target/linux/qualcommax/patches-6.1/0058-v6.9-arm64-dts-qcom-ipq6018-add-tsens-node.patch b/target/linux/qualcommax/patches-6.1/0058-v6.9-arm64-dts-qcom-ipq6018-add-tsens-node.patch
new file mode 100644
index 0000000000..9de90e4da6
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0058-v6.9-arm64-dts-qcom-ipq6018-add-tsens-node.patch
@@ -0,0 +1,34 @@
+From 0b17197055b528da22e9385200e61b847b499d48 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Thu, 25 Jan 2024 11:04:11 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: add tsens node
+
+IPQ6018 has temperature sensing HW block compatible with IPQ8074. Add
+node for it.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+Link: https://lore.kernel.org/r/1706173452-1017-3-git-send-email-mantas@8devices.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -342,6 +342,16 @@
+ clock-names = "core";
+ };
+
++ tsens: thermal-sensor@4a9000 {
++ compatible = "qcom,ipq6018-tsens", "qcom,ipq8074-tsens";
++ reg = <0x0 0x004a9000 0x0 0x1000>,
++ <0x0 0x004a8000 0x0 0x1000>;
++ interrupts = <GIC_SPI 184 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "combined";
++ #qcom,sensors = <16>;
++ #thermal-sensor-cells = <1>;
++ };
++
+ cryptobam: dma-controller@704000 {
+ compatible = "qcom,bam-v1.7.0";
+ reg = <0x0 0x00704000 0x0 0x20000>;
diff --git a/target/linux/qualcommax/patches-6.1/0059-v6.9-arm64-dts-qcom-ipq6018-add-thermal-zones.patch b/target/linux/qualcommax/patches-6.1/0059-v6.9-arm64-dts-qcom-ipq6018-add-thermal-zones.patch
new file mode 100644
index 0000000000..dab433348a
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0059-v6.9-arm64-dts-qcom-ipq6018-add-thermal-zones.patch
@@ -0,0 +1,180 @@
+From 8f053e5616352943e16966f195f5a7a161e6fe7d Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Thu, 25 Jan 2024 11:04:12 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: add thermal zones
+
+Add thermal zones to make use of thermal sensors data. For CPU zone,
+add cooling device that uses CPU frequency scaling.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+Link: https://lore.kernel.org/r/1706173452-1017-4-git-send-email-mantas@8devices.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 121 ++++++++++++++++++++++++++
+ 1 file changed, 121 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -9,6 +9,7 @@
+ #include <dt-bindings/clock/qcom,gcc-ipq6018.h>
+ #include <dt-bindings/reset/qcom,gcc-ipq6018.h>
+ #include <dt-bindings/clock/qcom,apss-ipq.h>
++#include <dt-bindings/thermal/thermal.h>
+
+ / {
+ #address-cells = <2>;
+@@ -43,6 +44,7 @@
+ clock-names = "cpu";
+ operating-points-v2 = <&cpu_opp_table>;
+ cpu-supply = <&ipq6018_s2>;
++ #cooling-cells = <2>;
+ };
+
+ CPU1: cpu@1 {
+@@ -55,6 +57,7 @@
+ clock-names = "cpu";
+ operating-points-v2 = <&cpu_opp_table>;
+ cpu-supply = <&ipq6018_s2>;
++ #cooling-cells = <2>;
+ };
+
+ CPU2: cpu@2 {
+@@ -67,6 +70,7 @@
+ clock-names = "cpu";
+ operating-points-v2 = <&cpu_opp_table>;
+ cpu-supply = <&ipq6018_s2>;
++ #cooling-cells = <2>;
+ };
+
+ CPU3: cpu@3 {
+@@ -79,6 +83,7 @@
+ clock-names = "cpu";
+ operating-points-v2 = <&cpu_opp_table>;
+ cpu-supply = <&ipq6018_s2>;
++ #cooling-cells = <2>;
+ };
+
+ L2_0: l2-cache {
+@@ -888,6 +893,122 @@
+ };
+ };
+
++ thermal-zones {
++ nss-top-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 4>;
++
++ trips {
++ nss-top-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
++ };
++
++ nss-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 5>;
++
++ trips {
++ nss-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
++ };
++
++ wcss-phya0-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 7>;
++
++ trips {
++ wcss-phya0-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
++ };
++
++ wcss-phya1-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 8>;
++
++ trips {
++ wcss-phya1-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
++ };
++
++ cpu-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 13>;
++
++ trips {
++ cpu-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++
++ cpu_alert: cpu-passive {
++ temperature = <110000>;
++ hysteresis = <1000>;
++ type = "passive";
++ };
++ };
++
++ cooling-maps {
++ map0 {
++ trip = <&cpu_alert>;
++ cooling-device = <&CPU0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
++ <&CPU1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
++ <&CPU2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>,
++ <&CPU3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>;
++ };
++ };
++ };
++
++ lpass-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 14>;
++
++ trips {
++ lpass-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
++ };
++
++ ddrss-top-thermal {
++ polling-delay-passive = <250>;
++ polling-delay = <1000>;
++ thermal-sensors = <&tsens 15>;
++
++ trips {
++ ddrss-top-critical {
++ temperature = <125000>;
++ hysteresis = <1000>;
++ type = "critical";
++ };
++ };
++ };
++ };
++
+ timer {
+ compatible = "arm,armv8-timer";
+ interrupts = <GIC_PPI 2 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
diff --git a/target/linux/qualcommax/patches-6.1/0060-v6.9-clk-qcom-gcc-ipq6018-add-qdss_at-clock-needed-for-wi.patch b/target/linux/qualcommax/patches-6.1/0060-v6.9-clk-qcom-gcc-ipq6018-add-qdss_at-clock-needed-for-wi.patch
new file mode 100644
index 0000000000..e72d1180c5
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0060-v6.9-clk-qcom-gcc-ipq6018-add-qdss_at-clock-needed-for-wi.patch
@@ -0,0 +1,50 @@
+From fd712118aa1aa758da1fd1546b3f8a1b00e42cbc Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 23 Jan 2024 11:26:09 +0200
+Subject: [PATCH] clk: qcom: gcc-ipq6018: add qdss_at clock needed for wifi
+ operation
+
+Without it system hangs upon wifi firmware load. It should be enabled by
+remoteproc/wifi driver. Bindings already exist for it, so add it based
+on vendor code.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+Link: https://lore.kernel.org/r/1706001970-26032-1-git-send-email-mantas@8devices.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -3523,6 +3523,22 @@ static struct clk_branch gcc_prng_ahb_cl
+ },
+ };
+
++static struct clk_branch gcc_qdss_at_clk = {
++ .halt_reg = 0x29024,
++ .clkr = {
++ .enable_reg = 0x29024,
++ .enable_mask = BIT(0),
++ .hw.init = &(struct clk_init_data){
++ .name = "gcc_qdss_at_clk",
++ .parent_hws = (const struct clk_hw *[]){
++ &qdss_at_clk_src.clkr.hw },
++ .num_parents = 1,
++ .flags = CLK_SET_RATE_PARENT,
++ .ops = &clk_branch2_ops,
++ },
++ },
++};
++
+ static struct clk_branch gcc_qdss_dap_clk = {
+ .halt_reg = 0x29084,
+ .clkr = {
+@@ -4362,6 +4378,7 @@ static struct clk_regmap *gcc_ipq6018_cl
+ [GCC_SYS_NOC_PCIE0_AXI_CLK] = &gcc_sys_noc_pcie0_axi_clk.clkr,
+ [GCC_PCIE0_PIPE_CLK] = &gcc_pcie0_pipe_clk.clkr,
+ [GCC_PRNG_AHB_CLK] = &gcc_prng_ahb_clk.clkr,
++ [GCC_QDSS_AT_CLK] = &gcc_qdss_at_clk.clkr,
+ [GCC_QDSS_DAP_CLK] = &gcc_qdss_dap_clk.clkr,
+ [GCC_QPIC_AHB_CLK] = &gcc_qpic_ahb_clk.clkr,
+ [GCC_QPIC_CLK] = &gcc_qpic_clk.clkr,
diff --git a/target/linux/qualcommax/patches-6.1/0061-v6.8-phy-qcom-qmp-usb-fix-serdes-init-sequence-for-IPQ6018.patch b/target/linux/qualcommax/patches-6.1/0061-v6.8-phy-qcom-qmp-usb-fix-serdes-init-sequence-for-IPQ6018.patch
new file mode 100644
index 0000000000..97b46f74ef
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0061-v6.8-phy-qcom-qmp-usb-fix-serdes-init-sequence-for-IPQ6018.patch
@@ -0,0 +1,102 @@
+From 62a5df451ab911421da96655fcc4d1e269ff6e2f Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 23 Jan 2024 18:09:20 +0200
+Subject: [PATCH] phy: qcom-qmp-usb: fix serdes init sequence for IPQ6018
+
+Commit 23fd679249df ("phy: qcom-qmp: add USB3 PHY support for IPQ6018")
+noted that IPQ6018 init is identical to IPQ8074. Yet downstream uses
+separate serdes init sequence for IPQ6018. Since already existing IPQ9574
+serdes init sequence is identical, just reuse it and fix failing USB3 mode
+in IPQ6018.
+
+Fixes: 23fd679249df ("phy: qcom-qmp: add USB3 PHY support for IPQ6018")
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+Link: https://lore.kernel.org/r/1706026160-17520-3-git-send-email-mantas@8devices.com
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+---
+ drivers/phy/qualcomm/phy-qcom-qmp-usb.c | 20 +++++++++++++++++++-
+ 1 file changed, 19 insertions(+), 1 deletion(-)
+
+--- a/drivers/phy/qualcomm/phy-qcom-qmp-usb.c
++++ b/drivers/phy/qualcomm/phy-qcom-qmp-usb.c
+@@ -233,6 +233,43 @@ static const struct qmp_phy_init_tbl ipq
+ QMP_PHY_INIT_CFG(QPHY_V3_PCS_TXDEEMPH_M3P5DB_V0, 0x0f),
+ };
+
++static const struct qmp_phy_init_tbl ipq9574_usb3_serdes_tbl[] = {
++ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x1a),
++ QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
++ QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30),
++ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
++ QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
++ QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
++ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x06),
++ /* PLL and Loop filter settings */
++ QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x68),
++ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0xab),
++ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0xaa),
++ QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x02),
++ QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x09),
++ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
++ QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
++ QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0xa0),
++ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0xaa),
++ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x29),
++ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a),
++ /* SSC settings */
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x7d),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x0a),
++ QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x05),
++};
++
+ static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = {
+ QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
+ QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
+@@ -1591,6 +1628,26 @@ static const char * const qmp_phy_vreg_l
+ "vdda-phy", "vdda-pll",
+ };
+
++static const struct qmp_phy_cfg ipq6018_usb3phy_cfg = {
++ .lanes = 1,
++
++ .serdes_tbl = ipq9574_usb3_serdes_tbl,
++ .serdes_tbl_num = ARRAY_SIZE(ipq9574_usb3_serdes_tbl),
++ .tx_tbl = msm8996_usb3_tx_tbl,
++ .tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl),
++ .rx_tbl = ipq8074_usb3_rx_tbl,
++ .rx_tbl_num = ARRAY_SIZE(ipq8074_usb3_rx_tbl),
++ .pcs_tbl = ipq8074_usb3_pcs_tbl,
++ .pcs_tbl_num = ARRAY_SIZE(ipq8074_usb3_pcs_tbl),
++ .clk_list = msm8996_phy_clk_l,
++ .num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
++ .reset_list = msm8996_usb3phy_reset_l,
++ .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
++ .vreg_list = qmp_phy_vreg_l,
++ .num_vregs = ARRAY_SIZE(qmp_phy_vreg_l),
++ .regs = qmp_v3_usb3phy_regs_layout,
++};
++
+ static const struct qmp_phy_cfg ipq8074_usb3phy_cfg = {
+ .lanes = 1,
+
+@@ -2534,7 +2591,7 @@ static const struct of_device_id qmp_usb
+ .data = &msm8996_usb3phy_cfg,
+ }, {
+ .compatible = "qcom,ipq6018-qmp-usb3-phy",
+- .data = &ipq8074_usb3phy_cfg,
++ .data = &ipq6018_usb3phy_cfg,
+ }, {
+ .compatible = "qcom,sc7180-qmp-usb3-phy",
+ .data = &sc7180_usb3phy_cfg,
diff --git a/target/linux/qualcommax/patches-6.1/0062-v6.8-arm64-dts-qcom-ipq8074-Add-QUP4-SPI-node.patch b/target/linux/qualcommax/patches-6.1/0062-v6.8-arm64-dts-qcom-ipq8074-Add-QUP4-SPI-node.patch
new file mode 100644
index 0000000000..1525726640
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0062-v6.8-arm64-dts-qcom-ipq8074-Add-QUP4-SPI-node.patch
@@ -0,0 +1,38 @@
+From 6a25e70214fde6dcf900271c819c8d7fe7b9a4b0 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Thu, 23 Nov 2023 13:12:54 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: Add QUP4 SPI node
+
+Add node to support the QUP4 SPI controller inside of IPQ8074.
+Some devices use this bus to communicate to a Bluetooth controller.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+Link: https://lore.kernel.org/r/20231123121324.1046164-1-robimarko@gmail.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -529,6 +529,20 @@
+ status = "disabled";
+ };
+
++ blsp1_spi4: spi@78b8000 {
++ compatible = "qcom,spi-qup-v2.2.1";
++ #address-cells = <1>;
++ #size-cells = <0>;
++ reg = <0x78b8000 0x600>;
++ interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
++ clocks = <&gcc GCC_BLSP1_QUP4_SPI_APPS_CLK>,
++ <&gcc GCC_BLSP1_AHB_CLK>;
++ clock-names = "core", "iface";
++ dmas = <&blsp_dma 18>, <&blsp_dma 19>;
++ dma-names = "tx", "rx";
++ status = "disabled";
++ };
++
+ blsp1_i2c5: i2c@78b9000 {
+ compatible = "qcom,i2c-qup-v2.2.1";
+ #address-cells = <1>;
diff --git a/target/linux/qualcommax/patches-6.1/0100-clk-qcom-clk-rcg2-introduce-support-for-multiple-con.patch b/target/linux/qualcommax/patches-6.1/0100-clk-qcom-clk-rcg2-introduce-support-for-multiple-con.patch
new file mode 100644
index 0000000000..9aa0a7952c
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0100-clk-qcom-clk-rcg2-introduce-support-for-multiple-con.patch
@@ -0,0 +1,203 @@
+From 032be4f49dda786fea9e1501212f6cd09a7ded96 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 3 Nov 2022 14:49:43 +0100
+Subject: [PATCH] clk: qcom: clk-rcg2: introduce support for multiple conf for
+ same freq
+
+Some RCG frequency can be reached by multiple configuration.
+
+We currently declare multiple configuration for the same frequency but
+that is not supported and always the first configuration will be taken.
+
+These multiple configuration are needed as based on the current parent
+configuration, it may be needed to use a different configuration to
+reach the same frequency.
+
+To handle this introduce 2 new macro, FM and C.
+
+- FM is used to declare an empty freq_tbl with just the frequency and an
+ array of confs to insert all the config for the provided frequency.
+
+- C is used to declare a fre_conf where src, pre_div, m and n are
+ provided.
+
+The driver is changed to handle this special freq_tbl and select the
+correct config by calculating the final rate and deciding based on the
+one that is less different than the requested one.
+
+Tested-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/clk/qcom/clk-rcg.h | 14 ++++++-
+ drivers/clk/qcom/clk-rcg2.c | 84 +++++++++++++++++++++++++++++++++----
+ 2 files changed, 88 insertions(+), 10 deletions(-)
+
+--- a/drivers/clk/qcom/clk-rcg.h
++++ b/drivers/clk/qcom/clk-rcg.h
+@@ -7,7 +7,17 @@
+ #include <linux/clk-provider.h>
+ #include "clk-regmap.h"
+
+-#define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) }
++#define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n), 0, NULL }
++
++#define FM(_f, _confs) { .freq = (_f), .confs_num = ARRAY_SIZE(_confs), .confs = (_confs) }
++#define C(s, h, m, n) { (s), (2 * (h) - 1), (m), (n) }
++
++struct freq_conf {
++ u8 src;
++ u8 pre_div;
++ u16 m;
++ u16 n;
++};
+
+ struct freq_tbl {
+ unsigned long freq;
+@@ -15,6 +25,8 @@ struct freq_tbl {
+ u8 pre_div;
+ u16 m;
+ u16 n;
++ int confs_num;
++ const struct freq_conf *confs;
+ };
+
+ /**
+--- a/drivers/clk/qcom/clk-rcg2.c
++++ b/drivers/clk/qcom/clk-rcg2.c
+@@ -203,11 +203,60 @@ clk_rcg2_recalc_rate(struct clk_hw *hw,
+ return __clk_rcg2_recalc_rate(hw, parent_rate, cfg);
+ }
+
++static void
++clk_rcg2_select_conf(struct clk_hw *hw, struct freq_tbl *f_tbl,
++ const struct freq_tbl *f, unsigned long req_rate)
++{
++ unsigned long best_rate = 0, parent_rate, rate;
++ const struct freq_conf *conf, *best_conf;
++ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
++ struct clk_hw *p;
++ int index, i;
++
++ /* Search in each provided config the one that is near the wanted rate */
++ for (i = 0, conf = f->confs; i < f->confs_num; i++, conf++) {
++ index = qcom_find_src_index(hw, rcg->parent_map, conf->src);
++ if (index < 0)
++ continue;
++
++ p = clk_hw_get_parent_by_index(hw, index);
++ if (!p)
++ continue;
++
++ parent_rate = clk_hw_get_rate(p);
++ rate = calc_rate(parent_rate, conf->n, conf->m, conf->n, conf->pre_div);
++
++ if (rate == req_rate) {
++ best_conf = conf;
++ break;
++ }
++
++ if (abs(req_rate - rate) < abs(best_rate - rate)) {
++ best_rate = rate;
++ best_conf = conf;
++ }
++ }
++
++ /*
++ * Very unlikely.
++ * Force the first conf if we can't find a correct config.
++ */
++ if (unlikely(i == f->confs_num))
++ best_conf = f->confs;
++
++ /* Apply the config */
++ f_tbl->src = best_conf->src;
++ f_tbl->pre_div = best_conf->pre_div;
++ f_tbl->m = best_conf->m;
++ f_tbl->n = best_conf->n;
++}
++
+ static int _freq_tbl_determine_rate(struct clk_hw *hw, const struct freq_tbl *f,
+ struct clk_rate_request *req,
+ enum freq_policy policy)
+ {
+ unsigned long clk_flags, rate = req->rate;
++ struct freq_tbl f_tbl;
+ struct clk_hw *p;
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+ int index;
+@@ -226,7 +275,15 @@ static int _freq_tbl_determine_rate(stru
+ if (!f)
+ return -EINVAL;
+
+- index = qcom_find_src_index(hw, rcg->parent_map, f->src);
++ f_tbl = *f;
++ /*
++ * A single freq may be reached by multiple configuration.
++ * Try to find the bast one if we have this kind of freq_table.
++ */
++ if (f->confs)
++ clk_rcg2_select_conf(hw, &f_tbl, f, rate);
++
++ index = qcom_find_src_index(hw, rcg->parent_map, f_tbl.src);
+ if (index < 0)
+ return index;
+
+@@ -236,18 +293,18 @@ static int _freq_tbl_determine_rate(stru
+ return -EINVAL;
+
+ if (clk_flags & CLK_SET_RATE_PARENT) {
+- rate = f->freq;
+- if (f->pre_div) {
++ rate = f_tbl.freq;
++ if (f_tbl.pre_div) {
+ if (!rate)
+ rate = req->rate;
+ rate /= 2;
+- rate *= f->pre_div + 1;
++ rate *= f_tbl.pre_div + 1;
+ }
+
+- if (f->n) {
++ if (f_tbl.n) {
+ u64 tmp = rate;
+- tmp = tmp * f->n;
+- do_div(tmp, f->m);
++ tmp = tmp * f_tbl.n;
++ do_div(tmp, f_tbl.m);
+ rate = tmp;
+ }
+ } else {
+@@ -255,7 +312,7 @@ static int _freq_tbl_determine_rate(stru
+ }
+ req->best_parent_hw = p;
+ req->best_parent_rate = rate;
+- req->rate = f->freq;
++ req->rate = f_tbl.freq;
+
+ return 0;
+ }
+@@ -351,6 +408,7 @@ static int __clk_rcg2_set_rate(struct cl
+ {
+ struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+ const struct freq_tbl *f;
++ struct freq_tbl f_tbl;
+
+ switch (policy) {
+ case FLOOR:
+@@ -366,7 +424,15 @@ static int __clk_rcg2_set_rate(struct cl
+ if (!f)
+ return -EINVAL;
+
+- return clk_rcg2_configure(rcg, f);
++ f_tbl = *f;
++ /*
++ * A single freq may be reached by multiple configuration.
++ * Try to find the best one if we have this kind of freq_table.
++ */
++ if (f->confs)
++ clk_rcg2_select_conf(hw, &f_tbl, f, rate);
++
++ return clk_rcg2_configure(rcg, &f_tbl);
+ }
+
+ static int clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate,
diff --git a/target/linux/qualcommax/patches-6.1/0101-clk-qcom-gcc-ipq8074-rework-nss_port5-6-clock-to-mul.patch b/target/linux/qualcommax/patches-6.1/0101-clk-qcom-gcc-ipq8074-rework-nss_port5-6-clock-to-mul.patch
new file mode 100644
index 0000000000..62a30bbb22
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0101-clk-qcom-gcc-ipq8074-rework-nss_port5-6-clock-to-mul.patch
@@ -0,0 +1,129 @@
+From f778553f296792f4d1e8b3552603ad6116ea3eb3 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Thu, 3 Nov 2022 14:49:44 +0100
+Subject: [PATCH] clk: qcom: gcc-ipq8074: rework nss_port5/6 clock to multiple
+ conf
+
+Rework nss_port5/6 to use the new multiple configuration implementation
+and correctly fix the clocks for these port under some corner case.
+
+This is particularly relevant for device that have 2.5G or 10G port
+connected to port5 or port 6 on ipq8074. As the parent are shared
+across multiple port it may be required to select the correct
+configuration to accomplish the desired clock. Without this patch such
+port doesn't work in some specific ethernet speed as the clock will be
+set to the wrong frequency as we just select the first configuration for
+the related frequency instead of selecting the best one.
+
+Tested-by: Robert Marko <robimarko@gmail.com> # ipq8074 Qnap QHora-301W
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+---
+ drivers/clk/qcom/gcc-ipq8074.c | 64 +++++++++++++++++++++++++---------
+ 1 file changed, 48 insertions(+), 16 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq8074.c
++++ b/drivers/clk/qcom/gcc-ipq8074.c
+@@ -1676,13 +1676,21 @@ static struct clk_regmap_div nss_port4_t
+ },
+ };
+
++static const struct freq_conf ftbl_nss_port5_rx_clk_src_25[] = {
++ C(P_UNIPHY1_RX, 12.5, 0, 0),
++ C(P_UNIPHY0_RX, 5, 0, 0),
++};
++
++static const struct freq_conf ftbl_nss_port5_rx_clk_src_125[] = {
++ C(P_UNIPHY1_RX, 2.5, 0, 0),
++ C(P_UNIPHY0_RX, 1, 0, 0),
++};
++
+ static const struct freq_tbl ftbl_nss_port5_rx_clk_src[] = {
+ F(19200000, P_XO, 1, 0, 0),
+- F(25000000, P_UNIPHY1_RX, 12.5, 0, 0),
+- F(25000000, P_UNIPHY0_RX, 5, 0, 0),
++ FM(25000000, ftbl_nss_port5_rx_clk_src_25),
+ F(78125000, P_UNIPHY1_RX, 4, 0, 0),
+- F(125000000, P_UNIPHY1_RX, 2.5, 0, 0),
+- F(125000000, P_UNIPHY0_RX, 1, 0, 0),
++ FM(125000000, ftbl_nss_port5_rx_clk_src_125),
+ F(156250000, P_UNIPHY1_RX, 2, 0, 0),
+ F(312500000, P_UNIPHY1_RX, 1, 0, 0),
+ { }
+@@ -1738,13 +1746,21 @@ static struct clk_regmap_div nss_port5_r
+ },
+ };
+
++static struct freq_conf ftbl_nss_port5_tx_clk_src_25[] = {
++ C(P_UNIPHY1_TX, 12.5, 0, 0),
++ C(P_UNIPHY0_TX, 5, 0, 0),
++};
++
++static struct freq_conf ftbl_nss_port5_tx_clk_src_125[] = {
++ C(P_UNIPHY1_TX, 2.5, 0, 0),
++ C(P_UNIPHY0_TX, 1, 0, 0),
++};
++
+ static const struct freq_tbl ftbl_nss_port5_tx_clk_src[] = {
+ F(19200000, P_XO, 1, 0, 0),
+- F(25000000, P_UNIPHY1_TX, 12.5, 0, 0),
+- F(25000000, P_UNIPHY0_TX, 5, 0, 0),
++ FM(25000000, ftbl_nss_port5_tx_clk_src_25),
+ F(78125000, P_UNIPHY1_TX, 4, 0, 0),
+- F(125000000, P_UNIPHY1_TX, 2.5, 0, 0),
+- F(125000000, P_UNIPHY0_TX, 1, 0, 0),
++ FM(125000000, ftbl_nss_port5_tx_clk_src_125),
+ F(156250000, P_UNIPHY1_TX, 2, 0, 0),
+ F(312500000, P_UNIPHY1_TX, 1, 0, 0),
+ { }
+@@ -1800,13 +1816,21 @@ static struct clk_regmap_div nss_port5_t
+ },
+ };
+
++static struct freq_conf ftbl_nss_port6_rx_clk_src_25[] = {
++ C(P_UNIPHY2_RX, 5, 0, 0),
++ C(P_UNIPHY2_RX, 12.5, 0, 0),
++};
++
++static struct freq_conf ftbl_nss_port6_rx_clk_src_125[] = {
++ C(P_UNIPHY2_RX, 1, 0, 0),
++ C(P_UNIPHY2_RX, 2.5, 0, 0),
++};
++
+ static const struct freq_tbl ftbl_nss_port6_rx_clk_src[] = {
+ F(19200000, P_XO, 1, 0, 0),
+- F(25000000, P_UNIPHY2_RX, 5, 0, 0),
+- F(25000000, P_UNIPHY2_RX, 12.5, 0, 0),
++ FM(25000000, ftbl_nss_port6_rx_clk_src_25),
+ F(78125000, P_UNIPHY2_RX, 4, 0, 0),
+- F(125000000, P_UNIPHY2_RX, 1, 0, 0),
+- F(125000000, P_UNIPHY2_RX, 2.5, 0, 0),
++ FM(125000000, ftbl_nss_port6_rx_clk_src_125),
+ F(156250000, P_UNIPHY2_RX, 2, 0, 0),
+ F(312500000, P_UNIPHY2_RX, 1, 0, 0),
+ { }
+@@ -1857,13 +1881,21 @@ static struct clk_regmap_div nss_port6_r
+ },
+ };
+
++static struct freq_conf ftbl_nss_port6_tx_clk_src_25[] = {
++ C(P_UNIPHY2_TX, 5, 0, 0),
++ C(P_UNIPHY2_TX, 12.5, 0, 0),
++};
++
++static struct freq_conf ftbl_nss_port6_tx_clk_src_125[] = {
++ C(P_UNIPHY2_TX, 1, 0, 0),
++ C(P_UNIPHY2_TX, 2.5, 0, 0),
++};
++
+ static const struct freq_tbl ftbl_nss_port6_tx_clk_src[] = {
+ F(19200000, P_XO, 1, 0, 0),
+- F(25000000, P_UNIPHY2_TX, 5, 0, 0),
+- F(25000000, P_UNIPHY2_TX, 12.5, 0, 0),
++ FM(25000000, ftbl_nss_port6_tx_clk_src_25),
+ F(78125000, P_UNIPHY2_TX, 4, 0, 0),
+- F(125000000, P_UNIPHY2_TX, 1, 0, 0),
+- F(125000000, P_UNIPHY2_TX, 2.5, 0, 0),
++ FM(125000000, ftbl_nss_port6_tx_clk_src_125),
+ F(156250000, P_UNIPHY2_TX, 2, 0, 0),
+ F(312500000, P_UNIPHY2_TX, 1, 0, 0),
+ { }
diff --git a/target/linux/qualcommax/patches-6.1/0102-arm64-dts-ipq8074-add-reserved-memory-nodes.patch b/target/linux/qualcommax/patches-6.1/0102-arm64-dts-ipq8074-add-reserved-memory-nodes.patch
new file mode 100644
index 0000000000..3996d15d9d
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0102-arm64-dts-ipq8074-add-reserved-memory-nodes.patch
@@ -0,0 +1,60 @@
+From ad2d07f71739351eeea1d8a120c0918e2c4b265f Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 22 Dec 2021 12:23:34 +0100
+Subject: [PATCH] arm64: dts: ipq8074: add reserved memory nodes
+
+IPQ8074 has multiple reserved memory ranges, if they are not defined
+then weird things tend to happen, board hangs and resets when PCI or
+WLAN is used etc.
+
+So, to avoid all of that add the reserved memory nodes from the downstream
+5.4 kernel from QCA.
+This is their default layout meant for devices with 1GB of RAM, but
+devices with lower ammounts can override the Q6 node.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 35 +++++++++++++++++++++++++++
+ 1 file changed, 35 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -85,6 +85,16 @@
+ #size-cells = <2>;
+ ranges;
+
++ nss@40000000 {
++ no-map;
++ reg = <0x0 0x40000000 0x0 0x01000000>;
++ };
++
++ tzapp_region: tzapp@4a400000 {
++ no-map;
++ reg = <0x0 0x4a400000 0x0 0x00200000>;
++ };
++
+ bootloader@4a600000 {
+ reg = <0x0 0x4a600000 0x0 0x400000>;
+ no-map;
+@@ -107,6 +117,21 @@
+ reg = <0x0 0x4ac00000 0x0 0x400000>;
+ no-map;
+ };
++
++ q6_region: wcnss@4b000000 {
++ no-map;
++ reg = <0x0 0x4b000000 0x0 0x05f00000>;
++ };
++
++ q6_etr_region: q6_etr_dump@50f00000 {
++ no-map;
++ reg = <0x0 0x50f00000 0x0 0x00100000>;
++ };
++
++ m3_dump_region: m3_dump@51000000 {
++ no-map;
++ reg = <0x0 0x51000000 0x0 0x100000>;
++ };
+ };
+
+ firmware {
diff --git a/target/linux/qualcommax/patches-6.1/0110-arm64-dts-qcom-ipq8074-pass-QMP-PCI-PHY-PIPE-clocks-.patch b/target/linux/qualcommax/patches-6.1/0110-arm64-dts-qcom-ipq8074-pass-QMP-PCI-PHY-PIPE-clocks-.patch
new file mode 100644
index 0000000000..fd97663513
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0110-arm64-dts-qcom-ipq8074-pass-QMP-PCI-PHY-PIPE-clocks-.patch
@@ -0,0 +1,30 @@
+From 8a576b5bc9f0555d1d970cacabcaa24a3b74fa57 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 16 Nov 2022 22:15:01 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: pass QMP PCI PHY PIPE clocks to
+ GCC
+
+Pass QMP PCI PHY PIPE clocks to the GCC controller so it does not have to
+find them by matching globaly by name.
+
+If not passed directly, driver maintains backwards compatibility by then
+falling back to global lookup.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -399,8 +399,8 @@
+ gcc: gcc@1800000 {
+ compatible = "qcom,gcc-ipq8074";
+ reg = <0x01800000 0x80000>;
+- clocks = <&xo>, <&sleep_clk>;
+- clock-names = "xo", "sleep_clk";
++ clocks = <&xo>, <&sleep_clk>, <&pcie_phy0>, <&pcie_phy1>;
++ clock-names = "xo", "sleep_clk", "pcie0_pipe", "pcie1_pipe";
+ #clock-cells = <1>;
+ #power-domain-cells = <1>;
+ #reset-cells = <1>;
diff --git a/target/linux/qualcommax/patches-6.1/0111-arm64-dts-qcom-ipq8074-use-msi-parent-for-PCIe.patch b/target/linux/qualcommax/patches-6.1/0111-arm64-dts-qcom-ipq8074-use-msi-parent-for-PCIe.patch
new file mode 100644
index 0000000000..9163cd76fe
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0111-arm64-dts-qcom-ipq8074-use-msi-parent-for-PCIe.patch
@@ -0,0 +1,43 @@
+From fb1f6850be00d8dd8a54017be4c1336e224069ac Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 16 Nov 2022 22:26:25 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: use msi-parent for PCIe
+
+Instead of hardcoding the IRQ, simply use msi-parent instead.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -734,7 +734,7 @@
+ reg = <0x0b000000 0x1000>, <0x0b002000 0x1000>;
+ ranges = <0 0xb00a000 0xffd>;
+
+- v2m@0 {
++ gic_v2m0: v2m@0 {
+ compatible = "arm,gic-v2m-frame";
+ msi-controller;
+ reg = <0x0 0xffd>;
+@@ -847,8 +847,7 @@
+ ranges = <0x81000000 0x0 0x00000000 0x10200000 0x0 0x10000>, /* I/O */
+ <0x82000000 0x0 0x10220000 0x10220000 0x0 0xfde0000>; /* MEM */
+
+- interrupts = <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+- interrupt-names = "msi";
++ msi-parent = <&gic_v2m0>;
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 0x7>;
+ interrupt-map = <0 0 0 1 &intc 0 142
+@@ -909,8 +908,7 @@
+ ranges = <0x81000000 0x0 0x00000000 0x20200000 0x0 0x10000>, /* I/O */
+ <0x82000000 0x0 0x20220000 0x20220000 0x0 0xfde0000>; /* MEM */
+
+- interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>;
+- interrupt-names = "msi";
++ msi-parent = <&gic_v2m0>;
+ #interrupt-cells = <1>;
+ interrupt-map-mask = <0 0 0 0x7>;
+ interrupt-map = <0 0 0 1 &intc 0 75
diff --git a/target/linux/qualcommax/patches-6.1/0112-remoteproc-qcom-Add-PRNG-proxy-clock.patch b/target/linux/qualcommax/patches-6.1/0112-remoteproc-qcom-Add-PRNG-proxy-clock.patch
new file mode 100644
index 0000000000..0a984948b5
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0112-remoteproc-qcom-Add-PRNG-proxy-clock.patch
@@ -0,0 +1,155 @@
+From 125681433c8e526356947acf572fe8ca8ad32291 Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:05 +0530
+Subject: [PATCH] remoteproc: qcom: Add PRNG proxy clock
+
+PRNG clock is needed by the secure PIL, support for the same
+is added in subsequent patches.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 65 +++++++++++++++++++++--------
+ 1 file changed, 47 insertions(+), 18 deletions(-)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -91,19 +91,6 @@ enum {
+ WCSS_QCS404,
+ };
+
+-struct wcss_data {
+- const char *firmware_name;
+- unsigned int crash_reason_smem;
+- u32 version;
+- bool aon_reset_required;
+- bool wcss_q6_reset_required;
+- const char *ssr_name;
+- const char *sysmon_name;
+- int ssctl_id;
+- const struct rproc_ops *ops;
+- bool requires_force_stop;
+-};
+-
+ struct q6v5_wcss {
+ struct device *dev;
+
+@@ -128,6 +115,7 @@ struct q6v5_wcss {
+ struct clk *qdsp6ss_xo_cbcr;
+ struct clk *qdsp6ss_core_gfmux;
+ struct clk *lcc_bcr_sleep;
++ struct clk *prng_clk;
+ struct regulator *cx_supply;
+ struct qcom_sysmon *sysmon;
+
+@@ -151,6 +139,21 @@ struct q6v5_wcss {
+ struct qcom_rproc_ssr ssr_subdev;
+ };
+
++struct wcss_data {
++ int (*init_clock)(struct q6v5_wcss *wcss);
++ int (*init_regulator)(struct q6v5_wcss *wcss);
++ const char *firmware_name;
++ unsigned int crash_reason_smem;
++ u32 version;
++ bool aon_reset_required;
++ bool wcss_q6_reset_required;
++ const char *ssr_name;
++ const char *sysmon_name;
++ int ssctl_id;
++ const struct rproc_ops *ops;
++ bool requires_force_stop;
++};
++
+ static int q6v5_wcss_reset(struct q6v5_wcss *wcss)
+ {
+ int ret;
+@@ -240,6 +243,12 @@ static int q6v5_wcss_start(struct rproc
+ struct q6v5_wcss *wcss = rproc->priv;
+ int ret;
+
++ ret = clk_prepare_enable(wcss->prng_clk);
++ if (ret) {
++ dev_err(wcss->dev, "prng clock enable failed\n");
++ return ret;
++ }
++
+ qcom_q6v5_prepare(&wcss->q6v5);
+
+ /* Release Q6 and WCSS reset */
+@@ -733,6 +742,7 @@ static int q6v5_wcss_stop(struct rproc *
+ return ret;
+ }
+
++ clk_disable_unprepare(wcss->prng_clk);
+ qcom_q6v5_unprepare(&wcss->q6v5);
+
+ return 0;
+@@ -900,7 +910,21 @@ static int q6v5_alloc_memory_region(stru
+ return 0;
+ }
+
+-static int q6v5_wcss_init_clock(struct q6v5_wcss *wcss)
++static int ipq8074_init_clock(struct q6v5_wcss *wcss)
++{
++ int ret;
++
++ wcss->prng_clk = devm_clk_get(wcss->dev, "prng");
++ if (IS_ERR(wcss->prng_clk)) {
++ ret = PTR_ERR(wcss->prng_clk);
++ if (ret != -EPROBE_DEFER)
++ dev_err(wcss->dev, "Failed to get prng clock\n");
++ return ret;
++ }
++ return 0;
++}
++
++static int qcs404_init_clock(struct q6v5_wcss *wcss)
+ {
+ int ret;
+
+@@ -990,7 +1014,7 @@ static int q6v5_wcss_init_clock(struct q
+ return 0;
+ }
+
+-static int q6v5_wcss_init_regulator(struct q6v5_wcss *wcss)
++static int qcs404_init_regulator(struct q6v5_wcss *wcss)
+ {
+ wcss->cx_supply = devm_regulator_get(wcss->dev, "cx");
+ if (IS_ERR(wcss->cx_supply))
+@@ -1034,12 +1058,14 @@ static int q6v5_wcss_probe(struct platfo
+ if (ret)
+ goto free_rproc;
+
+- if (wcss->version == WCSS_QCS404) {
+- ret = q6v5_wcss_init_clock(wcss);
++ if (desc->init_clock) {
++ ret = desc->init_clock(wcss);
+ if (ret)
+ goto free_rproc;
++ }
+
+- ret = q6v5_wcss_init_regulator(wcss);
++ if (desc->init_regulator) {
++ ret = desc->init_regulator(wcss);
+ if (ret)
+ goto free_rproc;
+ }
+@@ -1087,6 +1113,7 @@ static int q6v5_wcss_remove(struct platf
+ }
+
+ static const struct wcss_data wcss_ipq8074_res_init = {
++ .init_clock = ipq8074_init_clock,
+ .firmware_name = "IPQ8074/q6_fw.mdt",
+ .crash_reason_smem = WCSS_CRASH_REASON,
+ .aon_reset_required = true,
+@@ -1096,6 +1123,8 @@ static const struct wcss_data wcss_ipq80
+ };
+
+ static const struct wcss_data wcss_qcs404_res_init = {
++ .init_clock = qcs404_init_clock,
++ .init_regulator = qcs404_init_regulator,
+ .crash_reason_smem = WCSS_CRASH_REASON,
+ .firmware_name = "wcnss.mdt",
+ .version = WCSS_QCS404,
diff --git a/target/linux/qualcommax/patches-6.1/0113-remoteproc-qcom-Add-secure-PIL-support.patch b/target/linux/qualcommax/patches-6.1/0113-remoteproc-qcom-Add-secure-PIL-support.patch
new file mode 100644
index 0000000000..0328efc041
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0113-remoteproc-qcom-Add-secure-PIL-support.patch
@@ -0,0 +1,143 @@
+From 7358d42dfbdfdb5d4f1d0d4c2e5c2bb4143a29b0 Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:06 +0530
+Subject: [PATCH] remoteproc: qcom: Add secure PIL support
+
+IPQ8074 uses secure PIL. Hence, adding the support for the same.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 43 +++++++++++++++++++++++++++--
+ 1 file changed, 40 insertions(+), 3 deletions(-)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -18,6 +18,7 @@
+ #include <linux/regulator/consumer.h>
+ #include <linux/reset.h>
+ #include <linux/soc/qcom/mdt_loader.h>
++#include <linux/qcom_scm.h>
+ #include "qcom_common.h"
+ #include "qcom_pil_info.h"
+ #include "qcom_q6v5.h"
+@@ -86,6 +87,9 @@
+ #define TCSR_WCSS_CLK_ENABLE 0x14
+
+ #define MAX_HALT_REG 3
++
++#define WCNSS_PAS_ID 6
++
+ enum {
+ WCSS_IPQ8074,
+ WCSS_QCS404,
+@@ -134,6 +138,7 @@ struct q6v5_wcss {
+ unsigned int crash_reason_smem;
+ u32 version;
+ bool requires_force_stop;
++ bool need_mem_protection;
+
+ struct qcom_rproc_glink glink_subdev;
+ struct qcom_rproc_ssr ssr_subdev;
+@@ -152,6 +157,7 @@ struct wcss_data {
+ int ssctl_id;
+ const struct rproc_ops *ops;
+ bool requires_force_stop;
++ bool need_mem_protection;
+ };
+
+ static int q6v5_wcss_reset(struct q6v5_wcss *wcss)
+@@ -251,6 +257,15 @@ static int q6v5_wcss_start(struct rproc
+
+ qcom_q6v5_prepare(&wcss->q6v5);
+
++ if (wcss->need_mem_protection) {
++ ret = qcom_scm_pas_auth_and_reset(WCNSS_PAS_ID);
++ if (ret) {
++ dev_err(wcss->dev, "wcss_reset failed\n");
++ return ret;
++ }
++ goto wait_for_reset;
++ }
++
+ /* Release Q6 and WCSS reset */
+ ret = reset_control_deassert(wcss->wcss_reset);
+ if (ret) {
+@@ -285,6 +300,7 @@ static int q6v5_wcss_start(struct rproc
+ if (ret)
+ goto wcss_q6_reset;
+
++wait_for_reset:
+ ret = qcom_q6v5_wait_for_start(&wcss->q6v5, 5 * HZ);
+ if (ret == -ETIMEDOUT)
+ dev_err(wcss->dev, "start timed out\n");
+@@ -718,6 +734,15 @@ static int q6v5_wcss_stop(struct rproc *
+ struct q6v5_wcss *wcss = rproc->priv;
+ int ret;
+
++ if (wcss->need_mem_protection) {
++ ret = qcom_scm_pas_shutdown(WCNSS_PAS_ID);
++ if (ret) {
++ dev_err(wcss->dev, "not able to shutdown\n");
++ return ret;
++ }
++ goto pas_done;
++ }
++
+ /* WCSS powerdown */
+ if (wcss->requires_force_stop) {
+ ret = qcom_q6v5_request_stop(&wcss->q6v5, NULL);
+@@ -742,6 +767,7 @@ static int q6v5_wcss_stop(struct rproc *
+ return ret;
+ }
+
++pas_done:
+ clk_disable_unprepare(wcss->prng_clk);
+ qcom_q6v5_unprepare(&wcss->q6v5);
+
+@@ -765,9 +791,15 @@ static int q6v5_wcss_load(struct rproc *
+ struct q6v5_wcss *wcss = rproc->priv;
+ int ret;
+
+- ret = qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
+- 0, wcss->mem_region, wcss->mem_phys,
+- wcss->mem_size, &wcss->mem_reloc);
++ if (wcss->need_mem_protection)
++ ret = qcom_mdt_load(wcss->dev, fw, rproc->firmware,
++ WCNSS_PAS_ID, wcss->mem_region,
++ wcss->mem_phys, wcss->mem_size,
++ &wcss->mem_reloc);
++ else
++ ret = qcom_mdt_load_no_init(wcss->dev, fw, rproc->firmware,
++ 0, wcss->mem_region, wcss->mem_phys,
++ wcss->mem_size, &wcss->mem_reloc);
+ if (ret)
+ return ret;
+
+@@ -1036,6 +1068,9 @@ static int q6v5_wcss_probe(struct platfo
+ if (!desc)
+ return -EINVAL;
+
++ if (desc->need_mem_protection && !qcom_scm_is_available())
++ return -EPROBE_DEFER;
++
+ rproc = rproc_alloc(&pdev->dev, pdev->name, desc->ops,
+ desc->firmware_name, sizeof(*wcss));
+ if (!rproc) {
+@@ -1049,6 +1084,7 @@ static int q6v5_wcss_probe(struct platfo
+
+ wcss->version = desc->version;
+ wcss->requires_force_stop = desc->requires_force_stop;
++ wcss->need_mem_protection = desc->need_mem_protection;
+
+ ret = q6v5_wcss_init_mmio(wcss, pdev);
+ if (ret)
+@@ -1120,6 +1156,7 @@ static const struct wcss_data wcss_ipq80
+ .wcss_q6_reset_required = true,
+ .ops = &q6v5_wcss_ipq8074_ops,
+ .requires_force_stop = true,
++ .need_mem_protection = true,
+ };
+
+ static const struct wcss_data wcss_qcs404_res_init = {
diff --git a/target/linux/qualcommax/patches-6.1/0114-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch b/target/linux/qualcommax/patches-6.1/0114-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch
new file mode 100644
index 0000000000..e5c9506c89
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0114-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch
@@ -0,0 +1,103 @@
+From b422c9d4f048b086ce83f44a7cfcddcce162897f Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:07 +0530
+Subject: [PATCH] remoteproc: qcom: Add support for split q6 + m3 wlan firmware
+
+IPQ8074 supports split firmware for q6 and m3 as well.
+So add support for loading the m3 firmware before q6.
+Now the drivers works fine for both split and unified
+firmwares.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 33 +++++++++++++++++++++++++----
+ 1 file changed, 29 insertions(+), 4 deletions(-)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -139,6 +139,7 @@ struct q6v5_wcss {
+ u32 version;
+ bool requires_force_stop;
+ bool need_mem_protection;
++ const char *m3_firmware_name;
+
+ struct qcom_rproc_glink glink_subdev;
+ struct qcom_rproc_ssr ssr_subdev;
+@@ -147,7 +148,8 @@ struct q6v5_wcss {
+ struct wcss_data {
+ int (*init_clock)(struct q6v5_wcss *wcss);
+ int (*init_regulator)(struct q6v5_wcss *wcss);
+- const char *firmware_name;
++ const char *q6_firmware_name;
++ const char *m3_firmware_name;
+ unsigned int crash_reason_smem;
+ u32 version;
+ bool aon_reset_required;
+@@ -789,8 +791,29 @@ static void *q6v5_wcss_da_to_va(struct r
+ static int q6v5_wcss_load(struct rproc *rproc, const struct firmware *fw)
+ {
+ struct q6v5_wcss *wcss = rproc->priv;
++ const struct firmware *m3_fw;
+ int ret;
+
++ if (wcss->m3_firmware_name) {
++ ret = request_firmware(&m3_fw, wcss->m3_firmware_name,
++ wcss->dev);
++ if (ret)
++ goto skip_m3;
++
++ ret = qcom_mdt_load_no_init(wcss->dev, m3_fw,
++ wcss->m3_firmware_name, 0,
++ wcss->mem_region, wcss->mem_phys,
++ wcss->mem_size, &wcss->mem_reloc);
++
++ release_firmware(m3_fw);
++
++ if (ret) {
++ dev_err(wcss->dev, "can't load m3_fw.bXX\n");
++ return ret;
++ }
++ }
++
++skip_m3:
+ if (wcss->need_mem_protection)
+ ret = qcom_mdt_load(wcss->dev, fw, rproc->firmware,
+ WCNSS_PAS_ID, wcss->mem_region,
+@@ -1072,7 +1095,7 @@ static int q6v5_wcss_probe(struct platfo
+ return -EPROBE_DEFER;
+
+ rproc = rproc_alloc(&pdev->dev, pdev->name, desc->ops,
+- desc->firmware_name, sizeof(*wcss));
++ desc->q6_firmware_name, sizeof(*wcss));
+ if (!rproc) {
+ dev_err(&pdev->dev, "failed to allocate rproc\n");
+ return -ENOMEM;
+@@ -1085,6 +1108,7 @@ static int q6v5_wcss_probe(struct platfo
+ wcss->version = desc->version;
+ wcss->requires_force_stop = desc->requires_force_stop;
+ wcss->need_mem_protection = desc->need_mem_protection;
++ wcss->m3_firmware_name = desc->m3_firmware_name;
+
+ ret = q6v5_wcss_init_mmio(wcss, pdev);
+ if (ret)
+@@ -1150,7 +1174,8 @@ static int q6v5_wcss_remove(struct platf
+
+ static const struct wcss_data wcss_ipq8074_res_init = {
+ .init_clock = ipq8074_init_clock,
+- .firmware_name = "IPQ8074/q6_fw.mdt",
++ .q6_firmware_name = "IPQ8074/q6_fw.mdt",
++ .m3_firmware_name = "IPQ8074/m3_fw.mdt",
+ .crash_reason_smem = WCSS_CRASH_REASON,
+ .aon_reset_required = true,
+ .wcss_q6_reset_required = true,
+@@ -1163,7 +1188,7 @@ static const struct wcss_data wcss_qcs40
+ .init_clock = qcs404_init_clock,
+ .init_regulator = qcs404_init_regulator,
+ .crash_reason_smem = WCSS_CRASH_REASON,
+- .firmware_name = "wcnss.mdt",
++ .q6_firmware_name = "wcnss.mdt",
+ .version = WCSS_QCS404,
+ .aon_reset_required = false,
+ .wcss_q6_reset_required = false,
diff --git a/target/linux/qualcommax/patches-6.1/0115-remoteproc-qcom-Add-ssr-subdevice-identifier.patch b/target/linux/qualcommax/patches-6.1/0115-remoteproc-qcom-Add-ssr-subdevice-identifier.patch
new file mode 100644
index 0000000000..be63d46e8e
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0115-remoteproc-qcom-Add-ssr-subdevice-identifier.patch
@@ -0,0 +1,24 @@
+From 3a8f67b4770c817b04794c9a02e3f88f85d86280 Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:08 +0530
+Subject: [PATCH] remoteproc: qcom: Add ssr subdevice identifier
+
+Add name for ssr subdevice on IPQ8074 SoC.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -1179,6 +1179,7 @@ static const struct wcss_data wcss_ipq80
+ .crash_reason_smem = WCSS_CRASH_REASON,
+ .aon_reset_required = true,
+ .wcss_q6_reset_required = true,
++ .ssr_name = "q6wcss",
+ .ops = &q6v5_wcss_ipq8074_ops,
+ .requires_force_stop = true,
+ .need_mem_protection = true,
diff --git a/target/linux/qualcommax/patches-6.1/0116-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch b/target/linux/qualcommax/patches-6.1/0116-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch
new file mode 100644
index 0000000000..f0b717210f
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0116-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch
@@ -0,0 +1,79 @@
+From 8c73af6e8d78c66cfef0f551b00d375ec0b67ff3 Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:09 +0530
+Subject: [PATCH] remoteproc: qcom: Update regmap offsets for halt register
+
+Fixed issue in reading halt-regs parameter from device-tree.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 22 ++++++++++++++--------
+ 1 file changed, 14 insertions(+), 8 deletions(-)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -86,7 +86,7 @@
+ #define TCSR_WCSS_CLK_MASK 0x1F
+ #define TCSR_WCSS_CLK_ENABLE 0x14
+
+-#define MAX_HALT_REG 3
++#define MAX_HALT_REG 4
+
+ #define WCNSS_PAS_ID 6
+
+@@ -154,6 +154,7 @@ struct wcss_data {
+ u32 version;
+ bool aon_reset_required;
+ bool wcss_q6_reset_required;
++ bool bcr_reset_required;
+ const char *ssr_name;
+ const char *sysmon_name;
+ int ssctl_id;
+@@ -875,10 +876,13 @@ static int q6v5_wcss_init_reset(struct q
+ }
+ }
+
+- wcss->wcss_q6_bcr_reset = devm_reset_control_get_exclusive(dev, "wcss_q6_bcr_reset");
+- if (IS_ERR(wcss->wcss_q6_bcr_reset)) {
+- dev_err(wcss->dev, "unable to acquire wcss_q6_bcr_reset\n");
+- return PTR_ERR(wcss->wcss_q6_bcr_reset);
++ if (desc->bcr_reset_required) {
++ wcss->wcss_q6_bcr_reset = devm_reset_control_get_exclusive(dev,
++ "wcss_q6_bcr_reset");
++ if (IS_ERR(wcss->wcss_q6_bcr_reset)) {
++ dev_err(wcss->dev, "unable to acquire wcss_q6_bcr_reset\n");
++ return PTR_ERR(wcss->wcss_q6_bcr_reset);
++ }
+ }
+
+ return 0;
+@@ -929,9 +933,9 @@ static int q6v5_wcss_init_mmio(struct q6
+ return -EINVAL;
+ }
+
+- wcss->halt_q6 = halt_reg[0];
+- wcss->halt_wcss = halt_reg[1];
+- wcss->halt_nc = halt_reg[2];
++ wcss->halt_q6 = halt_reg[1];
++ wcss->halt_wcss = halt_reg[2];
++ wcss->halt_nc = halt_reg[3];
+
+ return 0;
+ }
+@@ -1179,6 +1183,7 @@ static const struct wcss_data wcss_ipq80
+ .crash_reason_smem = WCSS_CRASH_REASON,
+ .aon_reset_required = true,
+ .wcss_q6_reset_required = true,
++ .bcr_reset_required = false,
+ .ssr_name = "q6wcss",
+ .ops = &q6v5_wcss_ipq8074_ops,
+ .requires_force_stop = true,
+@@ -1193,6 +1198,7 @@ static const struct wcss_data wcss_qcs40
+ .version = WCSS_QCS404,
+ .aon_reset_required = false,
+ .wcss_q6_reset_required = false,
++ .bcr_reset_required = true,
+ .ssr_name = "mpss",
+ .sysmon_name = "wcnss",
+ .ssctl_id = 0x12,
diff --git a/target/linux/qualcommax/patches-6.1/0117-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch b/target/linux/qualcommax/patches-6.1/0117-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch
new file mode 100644
index 0000000000..fe0e0f9e0b
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0117-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch
@@ -0,0 +1,26 @@
+From ff7c6533ed8c4de58ed6c8aab03ea59c03eb4f31 Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:10 +0530
+Subject: [PATCH] dt-bindings: clock: qcom: Add reset for WCSSAON
+
+Add binding for WCSSAON reset required for Q6v5 reset on IPQ8074 SoC.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+Acked-by: Rob Herring <robh@kernel.org>
+Acked-by: Stephen Boyd <sboyd@kernel.org>
+---
+ include/dt-bindings/clock/qcom,gcc-ipq8074.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h
++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h
+@@ -381,6 +381,7 @@
+ #define GCC_NSSPORT4_RESET 143
+ #define GCC_NSSPORT5_RESET 144
+ #define GCC_NSSPORT6_RESET 145
++#define GCC_WCSSAON_RESET 146
+
+ #define USB0_GDSC 0
+ #define USB1_GDSC 1
diff --git a/target/linux/qualcommax/patches-6.1/0118-clk-qcom-Add-WCSSAON-reset.patch b/target/linux/qualcommax/patches-6.1/0118-clk-qcom-Add-WCSSAON-reset.patch
new file mode 100644
index 0000000000..be0524338d
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0118-clk-qcom-Add-WCSSAON-reset.patch
@@ -0,0 +1,25 @@
+From 43d9788f546d24df22d8ba3fcc2497d7ccc198f3 Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:11 +0530
+Subject: [PATCH] clk: qcom: Add WCSSAON reset
+
+Add WCSSAON reset required for Q6v5 on IPQ8074 SoC.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+Acked-by: Stephen Boyd <sboyd@kernel.org>
+---
+ drivers/clk/qcom/gcc-ipq8074.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/clk/qcom/gcc-ipq8074.c
++++ b/drivers/clk/qcom/gcc-ipq8074.c
+@@ -4711,6 +4711,7 @@ static const struct qcom_reset_map gcc_i
+ [GCC_NSSPORT4_RESET] = { .reg = 0x68014, .bitmask = BIT(27) | GENMASK(9, 8) },
+ [GCC_NSSPORT5_RESET] = { .reg = 0x68014, .bitmask = BIT(28) | GENMASK(11, 10) },
+ [GCC_NSSPORT6_RESET] = { .reg = 0x68014, .bitmask = BIT(29) | GENMASK(13, 12) },
++ [GCC_WCSSAON_RESET] = { 0x59010, 0 },
+ };
+
+ static struct gdsc *gcc_ipq8074_gdscs[] = {
diff --git a/target/linux/qualcommax/patches-6.1/0119-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch b/target/linux/qualcommax/patches-6.1/0119-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch
new file mode 100644
index 0000000000..8674522a11
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0119-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch
@@ -0,0 +1,48 @@
+From 406a332fd1bcc4e18d73cce390f56272fe9111d7 Mon Sep 17 00:00:00 2001
+From: Sivaprakash Murugesan <sivaprak@codeaurora.org>
+Date: Fri, 17 Apr 2020 16:37:10 +0530
+Subject: [PATCH] remoteproc: wcss: disable auto boot for IPQ8074
+
+There is no need for remoteproc to boot automatically, ath11k will trigger
+booting when its probing.
+
+Signed-off-by: Sivaprakash Murugesan <sivaprak@codeaurora.org>
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -161,6 +161,7 @@ struct wcss_data {
+ const struct rproc_ops *ops;
+ bool requires_force_stop;
+ bool need_mem_protection;
++ bool need_auto_boot;
+ };
+
+ static int q6v5_wcss_reset(struct q6v5_wcss *wcss)
+@@ -1150,6 +1151,7 @@ static int q6v5_wcss_probe(struct platfo
+ desc->sysmon_name,
+ desc->ssctl_id);
+
++ rproc->auto_boot = desc->need_auto_boot;
+ ret = rproc_add(rproc);
+ if (ret)
+ goto free_rproc;
+@@ -1188,6 +1190,7 @@ static const struct wcss_data wcss_ipq80
+ .ops = &q6v5_wcss_ipq8074_ops,
+ .requires_force_stop = true,
+ .need_mem_protection = true,
++ .need_auto_boot = false,
+ };
+
+ static const struct wcss_data wcss_qcs404_res_init = {
+@@ -1204,6 +1207,7 @@ static const struct wcss_data wcss_qcs40
+ .ssctl_id = 0x12,
+ .ops = &q6v5_wcss_qcs404_ops,
+ .requires_force_stop = false,
++ .need_auto_boot = true,
+ };
+
+ static const struct of_device_id q6v5_wcss_of_match[] = {
diff --git a/target/linux/qualcommax/patches-6.1/0120-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch b/target/linux/qualcommax/patches-6.1/0120-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch
new file mode 100644
index 0000000000..17ecd069a6
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0120-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch
@@ -0,0 +1,120 @@
+From 7388400b8bd42f71d040dbf2fdbdcb834fcc0ede Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Sat, 30 Jan 2021 10:50:13 +0530
+Subject: [PATCH] arm64: dts: qcom: Enable Q6v5 WCSS for ipq8074 SoC
+
+Enable remoteproc WCSS PIL driver with glink and ssr subdevices.
+Also enables smp2p and mailboxes required for IPC.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Signed-off-by: Sricharan R <sricharan@codeaurora.org>
+Signed-off-by: Nikhil Prakash V <nprakash@codeaurora.org>
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 81 +++++++++++++++++++++++++++
+ 1 file changed, 81 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -141,6 +141,32 @@
+ };
+ };
+
++ wcss: smp2p-wcss {
++ compatible = "qcom,smp2p";
++ qcom,smem = <435>, <428>;
++
++ interrupt-parent = <&intc>;
++ interrupts = <0 322 1>;
++
++ mboxes = <&apcs_glb 9>;
++
++ qcom,local-pid = <0>;
++ qcom,remote-pid = <1>;
++
++ wcss_smp2p_out: master-kernel {
++ qcom,entry-name = "master-kernel";
++ qcom,smp2p-feature-ssr-ack;
++ #qcom,smem-state-cells = <1>;
++ };
++
++ wcss_smp2p_in: slave-kernel {
++ qcom,entry-name = "slave-kernel";
++
++ interrupt-controller;
++ #interrupt-cells = <2>;
++ };
++ };
++
+ soc: soc {
+ #address-cells = <0x1>;
+ #size-cells = <0x1>;
+@@ -417,6 +443,11 @@
+ reg = <0x01937000 0x21000>;
+ };
+
++ tcsr_q6: syscon@1945000 {
++ compatible = "syscon";
++ reg = <0x01945000 0xe000>;
++ };
++
+ spmi_bus: spmi@200f000 {
+ compatible = "qcom,spmi-pmic-arb";
+ reg = <0x0200f000 0x001000>,
+@@ -949,6 +980,56 @@
+ "axi_s_sticky";
+ status = "disabled";
+ };
++
++ q6v5_wcss: q6v5_wcss@cd00000 {
++ compatible = "qcom,ipq8074-wcss-pil";
++ reg = <0x0cd00000 0x4040>,
++ <0x004ab000 0x20>;
++ reg-names = "qdsp6",
++ "rmb";
++ qca,auto-restart;
++ qca,extended-intc;
++ interrupts-extended = <&intc 0 325 1>,
++ <&wcss_smp2p_in 0 0>,
++ <&wcss_smp2p_in 1 0>,
++ <&wcss_smp2p_in 2 0>,
++ <&wcss_smp2p_in 3 0>;
++ interrupt-names = "wdog",
++ "fatal",
++ "ready",
++ "handover",
++ "stop-ack";
++
++ resets = <&gcc GCC_WCSSAON_RESET>,
++ <&gcc GCC_WCSS_BCR>,
++ <&gcc GCC_WCSS_Q6_BCR>;
++
++ reset-names = "wcss_aon_reset",
++ "wcss_reset",
++ "wcss_q6_reset";
++
++ clocks = <&gcc GCC_PRNG_AHB_CLK>;
++ clock-names = "prng";
++
++ qcom,halt-regs = <&tcsr_q6 0xa000 0xd000 0x0>;
++
++ qcom,smem-states = <&wcss_smp2p_out 0>,
++ <&wcss_smp2p_out 1>;
++ qcom,smem-state-names = "shutdown",
++ "stop";
++
++ memory-region = <&q6_region>;
++
++ glink-edge {
++ interrupts = <GIC_SPI 321 IRQ_TYPE_EDGE_RISING>;
++ qcom,remote-pid = <1>;
++ mboxes = <&apcs_glb 8>;
++
++ rpm_requests {
++ qcom,glink-channels = "IPCRTR";
++ };
++ };
++ };
+ };
+
+ timer {
diff --git a/target/linux/qualcommax/patches-6.1/0121-arm64-dts-ipq8074-Add-WLAN-node.patch b/target/linux/qualcommax/patches-6.1/0121-arm64-dts-ipq8074-Add-WLAN-node.patch
new file mode 100644
index 0000000000..76d3f35dd2
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0121-arm64-dts-ipq8074-Add-WLAN-node.patch
@@ -0,0 +1,135 @@
+From a67d1901741c162645eda0dbdc3a2c0c2aff5cf4 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Tue, 21 Dec 2021 14:49:36 +0100
+Subject: [PATCH] arm64: dts: ipq8074: Add WLAN node
+
+IPQ8074 has a AHB based Q6v5 802.11ax radios that are supported
+by the ath11k.
+
+Add the required DT node to enable the built-in radios.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 111 ++++++++++++++++++++++++++
+ 1 file changed, 111 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -1030,6 +1030,117 @@
+ };
+ };
+ };
++
++ wifi: wifi@c0000000 {
++ compatible = "qcom,ipq8074-wifi";
++ reg = <0xc000000 0x2000000>;
++
++ interrupts = <GIC_SPI 320 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 319 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 318 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 316 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 315 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 314 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 311 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 310 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 411 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 410 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 40 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 39 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 302 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 301 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 37 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 36 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 296 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 295 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 294 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 293 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 292 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 291 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 290 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 289 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 288 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 239 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 236 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 235 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 234 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 233 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 232 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 231 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 230 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 229 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 228 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 224 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 223 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 203 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 183 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 180 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 179 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 178 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 177 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 176 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 163 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 160 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 159 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 158 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 157 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 156 IRQ_TYPE_EDGE_RISING>;
++
++ interrupt-names = "misc-pulse1",
++ "misc-latch",
++ "sw-exception",
++ "ce0",
++ "ce1",
++ "ce2",
++ "ce3",
++ "ce4",
++ "ce5",
++ "ce6",
++ "ce7",
++ "ce8",
++ "ce9",
++ "ce10",
++ "ce11",
++ "host2wbm-desc-feed",
++ "host2reo-re-injection",
++ "host2reo-command",
++ "host2rxdma-monitor-ring3",
++ "host2rxdma-monitor-ring2",
++ "host2rxdma-monitor-ring1",
++ "reo2ost-exception",
++ "wbm2host-rx-release",
++ "reo2host-status",
++ "reo2host-destination-ring4",
++ "reo2host-destination-ring3",
++ "reo2host-destination-ring2",
++ "reo2host-destination-ring1",
++ "rxdma2host-monitor-destination-mac3",
++ "rxdma2host-monitor-destination-mac2",
++ "rxdma2host-monitor-destination-mac1",
++ "ppdu-end-interrupts-mac3",
++ "ppdu-end-interrupts-mac2",
++ "ppdu-end-interrupts-mac1",
++ "rxdma2host-monitor-status-ring-mac3",
++ "rxdma2host-monitor-status-ring-mac2",
++ "rxdma2host-monitor-status-ring-mac1",
++ "host2rxdma-host-buf-ring-mac3",
++ "host2rxdma-host-buf-ring-mac2",
++ "host2rxdma-host-buf-ring-mac1",
++ "rxdma2host-destination-ring-mac3",
++ "rxdma2host-destination-ring-mac2",
++ "rxdma2host-destination-ring-mac1",
++ "host2tcl-input-ring4",
++ "host2tcl-input-ring3",
++ "host2tcl-input-ring2",
++ "host2tcl-input-ring1",
++ "wbm2host-tx-completions-ring3",
++ "wbm2host-tx-completions-ring2",
++ "wbm2host-tx-completions-ring1",
++ "tcl2host-status-ring";
++ qcom,rproc = <&q6v5_wcss>;
++ status = "disabled";
++ };
+ };
+
+ timer {
diff --git a/target/linux/qualcommax/patches-6.1/0122-arm64-dts-ipq8074-add-CPU-clock.patch b/target/linux/qualcommax/patches-6.1/0122-arm64-dts-ipq8074-add-CPU-clock.patch
new file mode 100644
index 0000000000..a3c5f344ab
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0122-arm64-dts-ipq8074-add-CPU-clock.patch
@@ -0,0 +1,59 @@
+From cb3ef99c1553565e1dc0301ccd5c1c0fa2d15c15 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 31 Dec 2021 17:56:14 +0100
+Subject: [PATCH] arm64: dts: ipq8074: add CPU clock
+
+Now that CPU clock is exposed and can be controlled, add the necessary
+properties to the CPU nodes.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -5,6 +5,7 @@
+
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/clock/qcom,gcc-ipq8074.h>
++#include <dt-bindings/clock/qcom,apss-ipq.h>
+
+ / {
+ #address-cells = <2>;
+@@ -38,6 +39,8 @@
+ reg = <0x0>;
+ next-level-cache = <&L2_0>;
+ enable-method = "psci";
++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
++ clock-names = "cpu";
+ };
+
+ CPU1: cpu@1 {
+@@ -46,6 +49,8 @@
+ enable-method = "psci";
+ reg = <0x1>;
+ next-level-cache = <&L2_0>;
++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
++ clock-names = "cpu";
+ };
+
+ CPU2: cpu@2 {
+@@ -54,6 +59,8 @@
+ enable-method = "psci";
+ reg = <0x2>;
+ next-level-cache = <&L2_0>;
++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
++ clock-names = "cpu";
+ };
+
+ CPU3: cpu@3 {
+@@ -62,6 +69,8 @@
+ enable-method = "psci";
+ reg = <0x3>;
+ next-level-cache = <&L2_0>;
++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
++ clock-names = "cpu";
+ };
+
+ L2_0: l2-cache {
diff --git a/target/linux/qualcommax/patches-6.1/0123-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch b/target/linux/qualcommax/patches-6.1/0123-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch
new file mode 100644
index 0000000000..3520b38134
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0123-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch
@@ -0,0 +1,48 @@
+From 347ca56e86c99021fad059b9a8ef101245b8507e Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 31 Dec 2021 20:38:06 +0100
+Subject: [PATCH] arm64: dts: ipq8074: add cooling cells to CPU nodes
+
+Since there is CPU Freq support as well as thermal sensor support
+now for the IPQ8074, add cooling cells to CPU nodes so that they can
+be used as cooling devices using CPU Freq.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -41,6 +41,7 @@
+ enable-method = "psci";
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
++ #cooling-cells = <2>;
+ };
+
+ CPU1: cpu@1 {
+@@ -51,6 +52,7 @@
+ next-level-cache = <&L2_0>;
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
++ #cooling-cells = <2>;
+ };
+
+ CPU2: cpu@2 {
+@@ -61,6 +63,7 @@
+ next-level-cache = <&L2_0>;
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
++ #cooling-cells = <2>;
+ };
+
+ CPU3: cpu@3 {
+@@ -71,6 +74,7 @@
+ next-level-cache = <&L2_0>;
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
++ #cooling-cells = <2>;
+ };
+
+ L2_0: l2-cache {
diff --git a/target/linux/qualcommax/patches-6.1/0129-arm64-dts-qcom-ipq8074-add-QFPROM-fuses.patch b/target/linux/qualcommax/patches-6.1/0129-arm64-dts-qcom-ipq8074-add-QFPROM-fuses.patch
new file mode 100644
index 0000000000..8ec4660123
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0129-arm64-dts-qcom-ipq8074-add-QFPROM-fuses.patch
@@ -0,0 +1,128 @@
+From 04d2fc6a551bbd972a6428059b45ce79cb9de9d7 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Fri, 6 May 2022 22:38:24 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add QFPROM fuses
+
+Add the QFPROM node and CPR fuses.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 107 ++++++++++++++++++++++++++
+ 1 file changed, 107 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -343,6 +343,113 @@
+ status = "disabled";
+ };
+
++ qfprom: efuse@a4000 {
++ compatible = "qcom,ipq8074-qfprom", "qcom,qfprom";
++ reg = <0x000a4000 0x1000>;
++ #address-cells = <1>;
++ #size-cells = <1>;
++
++ cpr_efuse_speedbin: speedbin@125 {
++ reg = <0x125 0x1>;
++ bits = <0 3>;
++ };
++
++ cpr_efuse_boost_cfg: boost_cfg@125 {
++ reg = <0x125 0x1>;
++ bits = <3 3>;
++ };
++
++ cpr_efuse_misc_volt_adj: misc_volt_adj@125 {
++ reg = <0x125 0x1>;
++ bits = <3 3>;
++ };
++
++ cpr_efuse_boost_volt: boost_volt@126 {
++ reg = <0x126 0x1>;
++ bits = <6 1>;
++ };
++
++ cpr_efuse_revision: revision@23e {
++ reg = <0x23e 0x1>;
++ bits = <5 3>;
++ };
++
++ cpr_efuse_ro_sel0: rosel0@249 {
++ reg = <0x249 0x1>;
++ bits = <0 4>;
++ };
++
++ cpr_efuse_ro_sel1: rosel1@248 {
++ reg = <0x248 0x1>;
++ bits = <4 4>;
++ };
++
++ cpr_efuse_ro_sel2: rosel2@248 {
++ reg = <0x248 0x2>;
++ bits = <0 4>;
++ };
++
++ cpr_efuse_ro_sel3: rosel3@249 {
++ reg = <0x249 0x1>;
++ bits = <4 4>;
++ };
++
++ cpr_efuse_init_voltage0: ivoltage0@23a {
++ reg = <0x23a 0x1>;
++ bits = <2 6>;
++ };
++
++ cpr_efuse_init_voltage1: ivoltage1@239 {
++ reg = <0x239 0x2>;
++ bits = <4 6>;
++ };
++
++ cpr_efuse_init_voltage2: ivoltage2@238 {
++ reg = <0x238 0x2>;
++ bits = <6 6>;
++ };
++
++ cpr_efuse_init_voltage3: ivoltage3@238 {
++ reg = <0x238 0x1>;
++ bits = <0 6>;
++ };
++
++ cpr_efuse_quot0: quot0@244 {
++ reg = <0x244 0x2>;
++ bits = <0 12>;
++ };
++
++ cpr_efuse_quot1: quot1@242 {
++ reg = <0x242 0x2>;
++ bits = <4 12>;
++ };
++
++ cpr_efuse_quot2: quot2@241 {
++ reg = <0x241 0x2>;
++ bits = <0 12>;
++ };
++
++ cpr_efuse_quot3: quot3@245 {
++ reg = <0x245 0x2>;
++ bits = <4 12>;
++ };
++
++ cpr_efuse_quot0_offset: quot0_offset@23d {
++ reg = <0x23d 0x2>;
++ bits = <6 7>;
++ };
++
++ cpr_efuse_quot1_offset: quot1_offset@23c {
++ reg = <0x23c 0x2>;
++ bits = <7 7>;
++ };
++
++ cpr_efuse_quot2_offset: quot2_offset@23c {
++ reg = <0x23c 0x1>;
++ bits = <0 7>;
++ };
++ };
++
+ prng: rng@e3000 {
+ compatible = "qcom,prng-ee";
+ reg = <0x000e3000 0x1000>;
diff --git a/target/linux/qualcommax/patches-6.1/0130-arm64-dts-qcom-ipq8074-add-CPU-OPP-table.patch b/target/linux/qualcommax/patches-6.1/0130-arm64-dts-qcom-ipq8074-add-CPU-OPP-table.patch
new file mode 100644
index 0000000000..9c1e7b9d29
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0130-arm64-dts-qcom-ipq8074-add-CPU-OPP-table.patch
@@ -0,0 +1,102 @@
+From a20c4e8738a00087aa5d53fe5148ed484e23d229 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sat, 31 Dec 2022 13:56:26 +0100
+Subject: [PATCH] arm64: dts: qcom: ipq8074: add CPU OPP table
+
+Now that there is NVMEM CPUFreq support for IPQ8074, we can add the OPP
+table for SoC.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 52 +++++++++++++++++++++++++++
+ 1 file changed, 52 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -42,6 +42,7 @@
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
+ #cooling-cells = <2>;
++ operating-points-v2 = <&cpu_opp_table>;
+ };
+
+ CPU1: cpu@1 {
+@@ -53,6 +54,7 @@
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
+ #cooling-cells = <2>;
++ operating-points-v2 = <&cpu_opp_table>;
+ };
+
+ CPU2: cpu@2 {
+@@ -64,6 +66,7 @@
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
+ #cooling-cells = <2>;
++ operating-points-v2 = <&cpu_opp_table>;
+ };
+
+ CPU3: cpu@3 {
+@@ -75,6 +78,7 @@
+ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>;
+ clock-names = "cpu";
+ #cooling-cells = <2>;
++ operating-points-v2 = <&cpu_opp_table>;
+ };
+
+ L2_0: l2-cache {
+@@ -83,6 +87,54 @@
+ };
+ };
+
++ cpu_opp_table: opp-table {
++ compatible = "operating-points-v2-kryo-cpu";
++ nvmem-cells = <&cpr_efuse_speedbin>;
++ opp-shared;
++
++ opp-1017600000 {
++ opp-hz = /bits/ 64 <1017600000>;
++ opp-microvolt = <1>;
++ opp-supported-hw = <0xf>;
++ clock-latency-ns = <200000>;
++ };
++
++ opp-1382400000 {
++ opp-hz = /bits/ 64 <1382400000>;
++ opp-microvolt = <2>;
++ opp-supported-hw = <0xf>;
++ clock-latency-ns = <200000>;
++ };
++
++ opp-1651200000 {
++ opp-hz = /bits/ 64 <1651200000>;
++ opp-microvolt = <3>;
++ opp-supported-hw = <0x1>;
++ clock-latency-ns = <200000>;
++ };
++
++ opp-1843200000 {
++ opp-hz = /bits/ 64 <1843200000>;
++ opp-microvolt = <4>;
++ opp-supported-hw = <0x1>;
++ clock-latency-ns = <200000>;
++ };
++
++ opp-1920000000 {
++ opp-hz = /bits/ 64 <1920000000>;
++ opp-microvolt = <5>;
++ opp-supported-hw = <0x1>;
++ clock-latency-ns = <200000>;
++ };
++
++ opp-2208000000 {
++ opp-hz = /bits/ 64 <2208000000>;
++ opp-microvolt = <6>;
++ opp-supported-hw = <0x1>;
++ clock-latency-ns = <200000>;
++ };
++ };
++
+ pmu {
+ compatible = "arm,cortex-a53-pmu";
+ interrupts = <GIC_PPI 7 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
diff --git a/target/linux/qualcommax/patches-6.1/0136-remoteproc-qcom-wcss-populate-driver-data-for-IPQ601.patch b/target/linux/qualcommax/patches-6.1/0136-remoteproc-qcom-wcss-populate-driver-data-for-IPQ601.patch
new file mode 100644
index 0000000000..9a5a3407e1
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0136-remoteproc-qcom-wcss-populate-driver-data-for-IPQ601.patch
@@ -0,0 +1,61 @@
+From 9dd19a9ae36bc60d58287d0c52e53024d484e64d Mon Sep 17 00:00:00 2001
+From: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+Date: Fri, 29 Jan 2021 22:41:59 +0530
+Subject: [PATCH 2/3] remoteproc: qcom: wcss: populate driver data for IPQ6018
+
+Populate hardcoded param using driver data for IPQ6018 SoCs.
+
+Signed-off-by: Gokul Sriram Palanisamy <gokulsri@codeaurora.org>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 19 +++++++++++++++++--
+ 1 file changed, 17 insertions(+), 2 deletions(-)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -970,7 +970,7 @@ static int q6v5_alloc_memory_region(stru
+ return 0;
+ }
+
+-static int ipq8074_init_clock(struct q6v5_wcss *wcss)
++static int ipq_init_clock(struct q6v5_wcss *wcss)
+ {
+ int ret;
+
+@@ -1179,7 +1179,7 @@ static int q6v5_wcss_remove(struct platf
+ }
+
+ static const struct wcss_data wcss_ipq8074_res_init = {
+- .init_clock = ipq8074_init_clock,
++ .init_clock = ipq_init_clock,
+ .q6_firmware_name = "IPQ8074/q6_fw.mdt",
+ .m3_firmware_name = "IPQ8074/m3_fw.mdt",
+ .crash_reason_smem = WCSS_CRASH_REASON,
+@@ -1193,6 +1193,20 @@ static const struct wcss_data wcss_ipq80
+ .need_auto_boot = false,
+ };
+
++static const struct wcss_data wcss_ipq6018_res_init = {
++ .init_clock = ipq_init_clock,
++ .q6_firmware_name = "IPQ6018/q6_fw.mdt",
++ .m3_firmware_name = "IPQ6018/m3_fw.mdt",
++ .crash_reason_smem = WCSS_CRASH_REASON,
++ .aon_reset_required = true,
++ .wcss_q6_reset_required = true,
++ .bcr_reset_required = false,
++ .ssr_name = "q6wcss",
++ .ops = &q6v5_wcss_ipq8074_ops,
++ .requires_force_stop = true,
++ .need_mem_protection = true,
++};
++
+ static const struct wcss_data wcss_qcs404_res_init = {
+ .init_clock = qcs404_init_clock,
+ .init_regulator = qcs404_init_regulator,
+@@ -1212,6 +1226,7 @@ static const struct wcss_data wcss_qcs40
+
+ static const struct of_device_id q6v5_wcss_of_match[] = {
+ { .compatible = "qcom,ipq8074-wcss-pil", .data = &wcss_ipq8074_res_init },
++ { .compatible = "qcom,ipq6018-wcss-pil", .data = &wcss_ipq6018_res_init },
+ { .compatible = "qcom,qcs404-wcss-pil", .data = &wcss_qcs404_res_init },
+ { },
+ };
diff --git a/target/linux/qualcommax/patches-6.1/0137-arm64-dts-qcom-ipq6018-add-SDHCI-node.patch b/target/linux/qualcommax/patches-6.1/0137-arm64-dts-qcom-ipq6018-add-SDHCI-node.patch
new file mode 100644
index 0000000000..bfca74ba76
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0137-arm64-dts-qcom-ipq6018-add-SDHCI-node.patch
@@ -0,0 +1,45 @@
+From e4d7544ce092807e8c5aeb618cec30e2eb9b40c2 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Mon, 24 Apr 2023 15:13:32 +0300
+Subject: [PATCH 3/3] arm64: dts: qcom: ipq6018: add SDHCI node
+
+IPQ6018 has one SD/eMMC controller, add node for it.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+Tested-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 23 +++++++++++++++++++++++
+ 1 file changed, 23 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -469,6 +469,29 @@
+ };
+ };
+
++ sdhc_1: mmc@7804000 {
++ compatible = "qcom,ipq6018-sdhci", "qcom,sdhci-msm-v5";
++ reg = <0x0 0x07804000 0x0 0x1000>,
++ <0x0 0x07805000 0x0 0x1000>,
++ <0x0 0x07808000 0x0 0x2000>;
++ reg-names = "hc", "cqhci", "ice";
++
++ interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>,
++ <GIC_SPI 138 IRQ_TYPE_LEVEL_HIGH>;
++ interrupt-names = "hc_irq", "pwr_irq";
++
++ clocks = <&gcc GCC_SDCC1_AHB_CLK>,
++ <&gcc GCC_SDCC1_APPS_CLK>,
++ <&xo>,
++ <&gcc GCC_SDCC1_ICE_CORE_CLK>;
++ clock-names = "iface", "core", "xo", "ice";
++
++ resets = <&gcc GCC_SDCC1_BCR>;
++ supports-cqe;
++ bus-width = <8>;
++ status = "disabled";
++ };
++
+ blsp_dma: dma-controller@7884000 {
+ compatible = "qcom,bam-v1.7.0";
+ reg = <0x0 0x07884000 0x0 0x2b000>;
diff --git a/target/linux/qualcommax/patches-6.1/0139-arm64-dts-qcom-ipq6018-add-LDOA2-regulator.patch b/target/linux/qualcommax/patches-6.1/0139-arm64-dts-qcom-ipq6018-add-LDOA2-regulator.patch
new file mode 100644
index 0000000000..8889db97f9
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0139-arm64-dts-qcom-ipq6018-add-LDOA2-regulator.patch
@@ -0,0 +1,27 @@
+From d24bc08bfc66f47d6e0a294a080d62893a7696b5 Mon Sep 17 00:00:00 2001
+From: Chukun Pan <amadeus@jmu.edu.cn>
+Date: Thu, 18 Jan 2024 21:30:21 +0800
+Subject: [PATCH] arm64: dts: qcom: ipq6018: add LDOA2 regulator
+
+Add LDOA2 regulator of MP5496 to support SDCC voltage scaling.
+
+Suggested-by: Robert Marko <robimarko@gmail.com>
+Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -178,6 +178,11 @@
+ regulator-max-microvolt = <1062500>;
+ regulator-always-on;
+ };
++
++ ipq6018_l2: l2 {
++ regulator-min-microvolt = <1800000>;
++ regulator-max-microvolt = <3300000>;
++ };
+ };
+ };
+ };
diff --git a/target/linux/qualcommax/patches-6.1/0400-mtd-rawnand-add-support-for-TH58NYG3S0HBAI4.patch b/target/linux/qualcommax/patches-6.1/0400-mtd-rawnand-add-support-for-TH58NYG3S0HBAI4.patch
new file mode 100644
index 0000000000..c52681928b
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0400-mtd-rawnand-add-support-for-TH58NYG3S0HBAI4.patch
@@ -0,0 +1,42 @@
+From 8d8b37d3af2bdccf0a37d2017d876bfc6ce42552 Mon Sep 17 00:00:00 2001
+From: Chukun Pan <amadeus@jmu.edu.cn>
+Date: Fri, 20 Oct 2023 23:18:21 +0800
+Subject: [PATCH 1/1] mtd: rawnand: add support for TH58NYG3S0HBAI4 NAND flash
+
+The Toshiba TH58NYG3S0HBAI4 is detected with 128 byte OOB while the flash
+has 256 bytes OOB. Since it is not an ONFI compliant NAND, the model name
+cannot be read from anywhere, add a static NAND ID entry to correct this.
+
+However, the NAND ID of this flash is inconsistent with the datasheet.
+The actual NAND ID is only 4 ID bytes, the last ID byte is missing.
+
+Datasheet available at (the ID table is on page 50):
+https://europe.kioxia.com/content/dam/kioxia/newidr/productinfo/datasheet/201910/DST_TH58NYG3S0HBAI4-TDE_EN_31565.pdf
+
+Datasheet NAND ID: {0x98, 0xa3, 0x91, 0x26, 0x76}
+Actual NAND ID: {0x98, 0xa3, 0x91, 0x26}
+
+It seems that this flash may be counterfeit, but another Toshiba flash
+also has the same problem. Maybe the driver has a bug, or some Toshiba
+nand flash is like this. Anyway, add a static NAND ID entry with only
+4 ID bytes as a hack to make sure it works.
+
+Tested on Arcadyan AW1000 flashed with OpenWrt.
+
+Signed-off-by: Chukun Pan <amadeus@jmu.edu.cn>
+---
+ drivers/mtd/nand/raw/nand_ids.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/mtd/nand/raw/nand_ids.c
++++ b/drivers/mtd/nand/raw/nand_ids.c
+@@ -55,6 +55,9 @@ struct nand_flash_dev nand_flash_ids[] =
+ { .id = {0xad, 0xde, 0x14, 0xa7, 0x42, 0x4a} },
+ SZ_16K, SZ_8K, SZ_4M, NAND_NEED_SCRAMBLING, 6, 1664,
+ NAND_ECC_INFO(40, SZ_1K) },
++ {"TH58NYG3S0HBAI4 8G 1.8V 8-bit", /* Last ID bytes missing */
++ { .id = {0x98, 0xa3, 0x91, 0x26} },
++ SZ_4K, SZ_1K, SZ_256K, 0, 4, 256, NAND_ECC_INFO(8, SZ_512) },
+ {"TH58NVG2S3HBAI4 4G 3.3V 8-bit",
+ { .id = {0x98, 0xdc, 0x91, 0x15, 0x76} },
+ SZ_2K, SZ_512, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) },
diff --git a/target/linux/qualcommax/patches-6.1/0900-power-Add-Qualcomm-APM.patch b/target/linux/qualcommax/patches-6.1/0900-power-Add-Qualcomm-APM.patch
new file mode 100644
index 0000000000..2e5c72b7d1
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0900-power-Add-Qualcomm-APM.patch
@@ -0,0 +1,1047 @@
+From 6c98adf98236b8644b8f5e1aa7af9f1a88ea2766 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Mon, 11 Apr 2022 14:38:08 +0200
+Subject: [PATCH] power: Add Qualcomm APM
+
+Add Qualcomm APM driver, which allows scaling cache and memory fabrics.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/power/Kconfig | 1 +
+ drivers/power/Makefile | 1 +
+ drivers/power/qcom/Kconfig | 7 +
+ drivers/power/qcom/Makefile | 1 +
+ drivers/power/qcom/apm.c | 944 +++++++++++++++++++++++++++++++++
+ include/linux/power/qcom/apm.h | 48 ++
+ 6 files changed, 1002 insertions(+)
+ create mode 100644 drivers/power/qcom/Kconfig
+ create mode 100644 drivers/power/qcom/Makefile
+ create mode 100644 drivers/power/qcom/apm.c
+ create mode 100644 include/linux/power/qcom/apm.h
+
+--- a/drivers/power/Kconfig
++++ b/drivers/power/Kconfig
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ source "drivers/power/reset/Kconfig"
+ source "drivers/power/supply/Kconfig"
++source "drivers/power/qcom/Kconfig"
+--- a/drivers/power/Makefile
++++ b/drivers/power/Makefile
+@@ -1,3 +1,4 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ obj-$(CONFIG_POWER_RESET) += reset/
+ obj-$(CONFIG_POWER_SUPPLY) += supply/
++obj-$(CONFIG_QCOM_APM) += qcom/
+--- /dev/null
++++ b/drivers/power/qcom/Kconfig
+@@ -0,0 +1,7 @@
++config QCOM_APM
++ bool "Qualcomm Technologies Inc platform specific APM driver"
++ help
++ Platform specific driver to manage the power source of
++ memory arrays. Interfaces with regulator drivers to ensure
++ SRAM Vmin requirements are met across different performance
++ levels.
+--- /dev/null
++++ b/drivers/power/qcom/Makefile
+@@ -0,0 +1 @@
++obj-$(CONFIG_QCOM_APM) += apm.o
+--- /dev/null
++++ b/drivers/power/qcom/apm.c
+@@ -0,0 +1,944 @@
++/*
++ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#define pr_fmt(fmt) "%s: " fmt, __func__
++
++#include <linux/debugfs.h>
++#include <linux/delay.h>
++#include <linux/of_device.h>
++#include <linux/init.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/list.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++#include <linux/power/qcom/apm.h>
++
++/*
++ * VDD_APCC
++ * =============================================================
++ * | VDD_MX | |
++ * | ==========================|============= |
++ * ___|___ ___|___ ___|___ ___|___ ___|___ ___|___
++ * | | | | | | | | | | | |
++ * | APCC | | MX HS | | MX HS | | APCC | | MX HS | | APCC |
++ * | HS | | | | | | HS | | | | HS |
++ * |_______| |_______| |_______| |_______| |_______| |_______|
++ * |_________| |_________| |__________|
++ * | | |
++ * ______|_____ ______|_____ _______|_____
++ * | | | | | |
++ * | | | | | |
++ * | CPU MEM | | L2 MEM | | L3 MEM |
++ * | Arrays | | Arrays | | Arrays |
++ * | | | | | |
++ * |____________| |____________| |_____________|
++ *
++ */
++
++/* Register value definitions */
++#define APCS_GFMUXA_SEL_VAL 0x13
++#define APCS_GFMUXA_DESEL_VAL 0x03
++#define MSM_APM_MX_MODE_VAL 0x00
++#define MSM_APM_APCC_MODE_VAL 0x10
++#define MSM_APM_MX_DONE_VAL 0x00
++#define MSM_APM_APCC_DONE_VAL 0x03
++#define MSM_APM_OVERRIDE_SEL_VAL 0xb0
++#define MSM_APM_SEC_CLK_SEL_VAL 0x30
++#define SPM_EVENT_SET_VAL 0x01
++#define SPM_EVENT_CLEAR_VAL 0x00
++
++/* Register bit mask definitions */
++#define MSM_APM_CTL_STS_MASK 0x0f
++
++/* Register offset definitions */
++#define APCC_APM_MODE 0x00000098
++#define APCC_APM_CTL_STS 0x000000a8
++#define APCS_SPARE 0x00000068
++#define APCS_VERSION 0x00000fd0
++
++#define HMSS_VERSION_1P2 0x10020000
++
++#define MSM_APM_SWITCH_TIMEOUT_US 10
++#define SPM_WAKEUP_DELAY_US 2
++#define SPM_EVENT_NUM 6
++
++#define MSM_APM_DRIVER_NAME "qcom,msm-apm"
++
++enum {
++ MSM8996_ID,
++ MSM8953_ID,
++ IPQ807x_ID,
++};
++
++struct msm_apm_ctrl_dev {
++ struct list_head list;
++ struct device *dev;
++ enum msm_apm_supply supply;
++ spinlock_t lock;
++ void __iomem *reg_base;
++ void __iomem *apcs_csr_base;
++ void __iomem **apcs_spm_events_addr;
++ void __iomem *apc0_pll_ctl_addr;
++ void __iomem *apc1_pll_ctl_addr;
++ u32 version;
++ struct dentry *debugfs;
++ u32 msm_id;
++};
++
++#if defined(CONFIG_DEBUG_FS)
++static struct dentry *apm_debugfs_base;
++#endif
++
++static DEFINE_MUTEX(apm_ctrl_list_mutex);
++static LIST_HEAD(apm_ctrl_list);
++
++/*
++ * Get the resources associated with the APM controller from device tree
++ * and remap all I/O addresses that are relevant to this HW revision.
++ */
++static int msm_apm_ctrl_devm_ioremap(struct platform_device *pdev,
++ struct msm_apm_ctrl_dev *ctrl)
++{
++ struct device *dev = &pdev->dev;
++ struct resource *res;
++ static const char *res_name[SPM_EVENT_NUM] = {
++ "apc0-l2-spm",
++ "apc1-l2-spm",
++ "apc0-cpu0-spm",
++ "apc0-cpu1-spm",
++ "apc1-cpu0-spm",
++ "apc1-cpu1-spm"
++ };
++ int i, ret = 0;
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pm-apcc-glb");
++ if (!res) {
++ dev_err(dev, "Missing PM APCC Global register physical address");
++ return -EINVAL;
++ }
++ ctrl->reg_base = devm_ioremap(dev, res->start, resource_size(res));
++ if (!ctrl->reg_base) {
++ dev_err(dev, "Failed to map PM APCC Global registers\n");
++ return -ENOMEM;
++ }
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "apcs-csr");
++ if (!res) {
++ dev_err(dev, "Missing APCS CSR physical base address");
++ return -EINVAL;
++ }
++ ctrl->apcs_csr_base = devm_ioremap(dev, res->start, resource_size(res));
++ if (!ctrl->apcs_csr_base) {
++ dev_err(dev, "Failed to map APCS CSR registers\n");
++ return -ENOMEM;
++ }
++
++ ctrl->version = readl_relaxed(ctrl->apcs_csr_base + APCS_VERSION);
++
++ if (ctrl->version >= HMSS_VERSION_1P2)
++ return ret;
++
++ ctrl->apcs_spm_events_addr = devm_kzalloc(&pdev->dev,
++ SPM_EVENT_NUM
++ * sizeof(void __iomem *),
++ GFP_KERNEL);
++ if (!ctrl->apcs_spm_events_addr) {
++ dev_err(dev, "Failed to allocate memory for APCS SPM event registers\n");
++ return -ENOMEM;
++ }
++
++ for (i = 0; i < SPM_EVENT_NUM; i++) {
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
++ res_name[i]);
++ if (!res) {
++ dev_err(dev, "Missing address for %s\n", res_name[i]);
++ ret = -EINVAL;
++ goto free_events;
++ }
++
++ ctrl->apcs_spm_events_addr[i] = devm_ioremap(dev, res->start,
++ resource_size(res));
++ if (!ctrl->apcs_spm_events_addr[i]) {
++ dev_err(dev, "Failed to map %s\n", res_name[i]);
++ ret = -ENOMEM;
++ goto free_events;
++ }
++
++ dev_dbg(dev, "%s event phys: %pa virt:0x%p\n", res_name[i],
++ &res->start, ctrl->apcs_spm_events_addr[i]);
++ }
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
++ "apc0-pll-ctl");
++ if (!res) {
++ dev_err(dev, "Missing APC0 PLL CTL physical address\n");
++ ret = -EINVAL;
++ goto free_events;
++ }
++
++ ctrl->apc0_pll_ctl_addr = devm_ioremap(dev,
++ res->start,
++ resource_size(res));
++ if (!ctrl->apc0_pll_ctl_addr) {
++ dev_err(dev, "Failed to map APC0 PLL CTL register\n");
++ ret = -ENOMEM;
++ goto free_events;
++ }
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
++ "apc1-pll-ctl");
++ if (!res) {
++ dev_err(dev, "Missing APC1 PLL CTL physical address\n");
++ ret = -EINVAL;
++ goto free_events;
++ }
++
++ ctrl->apc1_pll_ctl_addr = devm_ioremap(dev,
++ res->start,
++ resource_size(res));
++ if (!ctrl->apc1_pll_ctl_addr) {
++ dev_err(dev, "Failed to map APC1 PLL CTL register\n");
++ ret = -ENOMEM;
++ goto free_events;
++ }
++
++ return ret;
++
++free_events:
++ devm_kfree(dev, ctrl->apcs_spm_events_addr);
++ return ret;
++}
++
++/* 8953 register offset definition */
++#define MSM8953_APM_DLY_CNTR 0x2ac
++
++/* Register field shift definitions */
++#define APM_CTL_SEL_SWITCH_DLY_SHIFT 0
++#define APM_CTL_RESUME_CLK_DLY_SHIFT 8
++#define APM_CTL_HALT_CLK_DLY_SHIFT 16
++#define APM_CTL_POST_HALT_DLY_SHIFT 24
++
++/* Register field mask definitions */
++#define APM_CTL_SEL_SWITCH_DLY_MASK GENMASK(7, 0)
++#define APM_CTL_RESUME_CLK_DLY_MASK GENMASK(15, 8)
++#define APM_CTL_HALT_CLK_DLY_MASK GENMASK(23, 16)
++#define APM_CTL_POST_HALT_DLY_MASK GENMASK(31, 24)
++
++/*
++ * Get the resources associated with the msm8953 APM controller from
++ * device tree, remap all I/O addresses, and program the initial
++ * register configuration required for the 8953 APM controller device.
++ */
++static int msm8953_apm_ctrl_init(struct platform_device *pdev,
++ struct msm_apm_ctrl_dev *ctrl)
++{
++ struct device *dev = &pdev->dev;
++ struct resource *res;
++ u32 delay_counter, val = 0, regval = 0;
++ int rc = 0;
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pm-apcc-glb");
++ if (!res) {
++ dev_err(dev, "Missing PM APCC Global register physical address\n");
++ return -ENODEV;
++ }
++ ctrl->reg_base = devm_ioremap(dev, res->start, resource_size(res));
++ if (!ctrl->reg_base) {
++ dev_err(dev, "Failed to map PM APCC Global registers\n");
++ return -ENOMEM;
++ }
++
++ /*
++ * Initial APM register configuration required before starting
++ * APM HW controller.
++ */
++ regval = readl_relaxed(ctrl->reg_base + MSM8953_APM_DLY_CNTR);
++ val = regval;
++
++ if (of_find_property(dev->of_node, "qcom,apm-post-halt-delay", NULL)) {
++ rc = of_property_read_u32(dev->of_node,
++ "qcom,apm-post-halt-delay", &delay_counter);
++ if (rc < 0) {
++ dev_err(dev, "apm-post-halt-delay read failed, rc = %d",
++ rc);
++ return rc;
++ }
++
++ val &= ~APM_CTL_POST_HALT_DLY_MASK;
++ val |= (delay_counter << APM_CTL_POST_HALT_DLY_SHIFT)
++ & APM_CTL_POST_HALT_DLY_MASK;
++ }
++
++ if (of_find_property(dev->of_node, "qcom,apm-halt-clk-delay", NULL)) {
++ rc = of_property_read_u32(dev->of_node,
++ "qcom,apm-halt-clk-delay", &delay_counter);
++ if (rc < 0) {
++ dev_err(dev, "apm-halt-clk-delay read failed, rc = %d",
++ rc);
++ return rc;
++ }
++
++ val &= ~APM_CTL_HALT_CLK_DLY_MASK;
++ val |= (delay_counter << APM_CTL_HALT_CLK_DLY_SHIFT)
++ & APM_CTL_HALT_CLK_DLY_MASK;
++ }
++
++ if (of_find_property(dev->of_node, "qcom,apm-resume-clk-delay", NULL)) {
++ rc = of_property_read_u32(dev->of_node,
++ "qcom,apm-resume-clk-delay", &delay_counter);
++ if (rc < 0) {
++ dev_err(dev, "apm-resume-clk-delay read failed, rc = %d",
++ rc);
++ return rc;
++ }
++
++ val &= ~APM_CTL_RESUME_CLK_DLY_MASK;
++ val |= (delay_counter << APM_CTL_RESUME_CLK_DLY_SHIFT)
++ & APM_CTL_RESUME_CLK_DLY_MASK;
++ }
++
++ if (of_find_property(dev->of_node, "qcom,apm-sel-switch-delay", NULL)) {
++ rc = of_property_read_u32(dev->of_node,
++ "qcom,apm-sel-switch-delay", &delay_counter);
++ if (rc < 0) {
++ dev_err(dev, "apm-sel-switch-delay read failed, rc = %d",
++ rc);
++ return rc;
++ }
++
++ val &= ~APM_CTL_SEL_SWITCH_DLY_MASK;
++ val |= (delay_counter << APM_CTL_SEL_SWITCH_DLY_SHIFT)
++ & APM_CTL_SEL_SWITCH_DLY_MASK;
++ }
++
++ if (val != regval) {
++ writel_relaxed(val, ctrl->reg_base + MSM8953_APM_DLY_CNTR);
++ /* make sure write completes before return */
++ mb();
++ }
++
++ return rc;
++}
++
++static int msm8996_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ int i, timeout = MSM_APM_SWITCH_TIMEOUT_US;
++ u32 regval;
++ int ret = 0;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ctrl_dev->lock, flags);
++
++ /* Perform revision-specific programming steps */
++ if (ctrl_dev->version < HMSS_VERSION_1P2) {
++ /* Clear SPM events */
++ for (i = 0; i < SPM_EVENT_NUM; i++)
++ writel_relaxed(SPM_EVENT_CLEAR_VAL,
++ ctrl_dev->apcs_spm_events_addr[i]);
++
++ udelay(SPM_WAKEUP_DELAY_US);
++
++ /* Switch APC/CBF to GPLL0 clock */
++ writel_relaxed(APCS_GFMUXA_SEL_VAL,
++ ctrl_dev->apcs_csr_base + APCS_SPARE);
++ ndelay(200);
++ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
++ ctrl_dev->apc0_pll_ctl_addr);
++ ndelay(200);
++ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
++ ctrl_dev->apc1_pll_ctl_addr);
++
++ /* Ensure writes complete before proceeding */
++ mb();
++ }
++
++ /* Switch arrays to MX supply and wait for its completion */
++ writel_relaxed(MSM_APM_MX_MODE_VAL, ctrl_dev->reg_base +
++ APCC_APM_MODE);
++
++ /* Ensure write above completes before delaying */
++ mb();
++
++ while (timeout > 0) {
++ regval = readl_relaxed(ctrl_dev->reg_base + APCC_APM_CTL_STS);
++ if ((regval & MSM_APM_CTL_STS_MASK) ==
++ MSM_APM_MX_DONE_VAL)
++ break;
++
++ udelay(1);
++ timeout--;
++ }
++
++ if (timeout == 0) {
++ ret = -ETIMEDOUT;
++ dev_err(ctrl_dev->dev, "APCC to MX APM switch timed out. APCC_APM_CTL_STS=0x%x\n",
++ regval);
++ }
++
++ /* Perform revision-specific programming steps */
++ if (ctrl_dev->version < HMSS_VERSION_1P2) {
++ /* Switch APC/CBF clocks to original source */
++ writel_relaxed(APCS_GFMUXA_DESEL_VAL,
++ ctrl_dev->apcs_csr_base + APCS_SPARE);
++ ndelay(200);
++ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
++ ctrl_dev->apc0_pll_ctl_addr);
++ ndelay(200);
++ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
++ ctrl_dev->apc1_pll_ctl_addr);
++
++ /* Complete clock source switch before SPM event sequence */
++ mb();
++
++ /* Set SPM events */
++ for (i = 0; i < SPM_EVENT_NUM; i++)
++ writel_relaxed(SPM_EVENT_SET_VAL,
++ ctrl_dev->apcs_spm_events_addr[i]);
++ }
++
++ if (!ret) {
++ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
++ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
++ }
++
++ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
++
++ return ret;
++}
++
++static int msm8996_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ int i, timeout = MSM_APM_SWITCH_TIMEOUT_US;
++ u32 regval;
++ int ret = 0;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ctrl_dev->lock, flags);
++
++ /* Perform revision-specific programming steps */
++ if (ctrl_dev->version < HMSS_VERSION_1P2) {
++ /* Clear SPM events */
++ for (i = 0; i < SPM_EVENT_NUM; i++)
++ writel_relaxed(SPM_EVENT_CLEAR_VAL,
++ ctrl_dev->apcs_spm_events_addr[i]);
++
++ udelay(SPM_WAKEUP_DELAY_US);
++
++ /* Switch APC/CBF to GPLL0 clock */
++ writel_relaxed(APCS_GFMUXA_SEL_VAL,
++ ctrl_dev->apcs_csr_base + APCS_SPARE);
++ ndelay(200);
++ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
++ ctrl_dev->apc0_pll_ctl_addr);
++ ndelay(200);
++ writel_relaxed(MSM_APM_OVERRIDE_SEL_VAL,
++ ctrl_dev->apc1_pll_ctl_addr);
++
++ /* Ensure previous writes complete before proceeding */
++ mb();
++ }
++
++ /* Switch arrays to APCC supply and wait for its completion */
++ writel_relaxed(MSM_APM_APCC_MODE_VAL, ctrl_dev->reg_base +
++ APCC_APM_MODE);
++
++ /* Ensure write above completes before delaying */
++ mb();
++
++ while (timeout > 0) {
++ regval = readl_relaxed(ctrl_dev->reg_base + APCC_APM_CTL_STS);
++ if ((regval & MSM_APM_CTL_STS_MASK) ==
++ MSM_APM_APCC_DONE_VAL)
++ break;
++
++ udelay(1);
++ timeout--;
++ }
++
++ if (timeout == 0) {
++ ret = -ETIMEDOUT;
++ dev_err(ctrl_dev->dev, "MX to APCC APM switch timed out. APCC_APM_CTL_STS=0x%x\n",
++ regval);
++ }
++
++ /* Perform revision-specific programming steps */
++ if (ctrl_dev->version < HMSS_VERSION_1P2) {
++ /* Set SPM events */
++ for (i = 0; i < SPM_EVENT_NUM; i++)
++ writel_relaxed(SPM_EVENT_SET_VAL,
++ ctrl_dev->apcs_spm_events_addr[i]);
++
++ /* Complete SPM event sequence before clock source switch */
++ mb();
++
++ /* Switch APC/CBF clocks to original source */
++ writel_relaxed(APCS_GFMUXA_DESEL_VAL,
++ ctrl_dev->apcs_csr_base + APCS_SPARE);
++ ndelay(200);
++ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
++ ctrl_dev->apc0_pll_ctl_addr);
++ ndelay(200);
++ writel_relaxed(MSM_APM_SEC_CLK_SEL_VAL,
++ ctrl_dev->apc1_pll_ctl_addr);
++ }
++
++ if (!ret) {
++ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
++ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
++ }
++
++ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
++
++ return ret;
++}
++
++/* 8953 register value definitions */
++#define MSM8953_APM_MX_MODE_VAL 0x00
++#define MSM8953_APM_APCC_MODE_VAL 0x02
++#define MSM8953_APM_MX_DONE_VAL 0x00
++#define MSM8953_APM_APCC_DONE_VAL 0x03
++
++/* 8953 register offset definitions */
++#define MSM8953_APCC_APM_MODE 0x000002a8
++#define MSM8953_APCC_APM_CTL_STS 0x000002b0
++
++/* 8953 constants */
++#define MSM8953_APM_SWITCH_TIMEOUT_US 500
++
++/* Register bit mask definitions */
++#define MSM8953_APM_CTL_STS_MASK 0x1f
++
++static int msm8953_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ int timeout = MSM8953_APM_SWITCH_TIMEOUT_US;
++ u32 regval;
++ int ret = 0;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ctrl_dev->lock, flags);
++
++ /* Switch arrays to MX supply and wait for its completion */
++ writel_relaxed(MSM8953_APM_MX_MODE_VAL, ctrl_dev->reg_base +
++ MSM8953_APCC_APM_MODE);
++
++ /* Ensure write above completes before delaying */
++ mb();
++
++ while (timeout > 0) {
++ regval = readl_relaxed(ctrl_dev->reg_base +
++ MSM8953_APCC_APM_CTL_STS);
++ if ((regval & MSM8953_APM_CTL_STS_MASK) ==
++ MSM8953_APM_MX_DONE_VAL)
++ break;
++
++ udelay(1);
++ timeout--;
++ }
++
++ if (timeout == 0) {
++ ret = -ETIMEDOUT;
++ dev_err(ctrl_dev->dev, "APCC to MX APM switch timed out. APCC_APM_CTL_STS=0x%x\n",
++ regval);
++ } else {
++ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
++ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
++ }
++
++ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
++
++ return ret;
++}
++
++static int msm8953_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ int timeout = MSM8953_APM_SWITCH_TIMEOUT_US;
++ u32 regval;
++ int ret = 0;
++ unsigned long flags;
++
++ spin_lock_irqsave(&ctrl_dev->lock, flags);
++
++ /* Switch arrays to APCC supply and wait for its completion */
++ writel_relaxed(MSM8953_APM_APCC_MODE_VAL, ctrl_dev->reg_base +
++ MSM8953_APCC_APM_MODE);
++
++ /* Ensure write above completes before delaying */
++ mb();
++
++ while (timeout > 0) {
++ regval = readl_relaxed(ctrl_dev->reg_base +
++ MSM8953_APCC_APM_CTL_STS);
++ if ((regval & MSM8953_APM_CTL_STS_MASK) ==
++ MSM8953_APM_APCC_DONE_VAL)
++ break;
++
++ udelay(1);
++ timeout--;
++ }
++
++ if (timeout == 0) {
++ ret = -ETIMEDOUT;
++ dev_err(ctrl_dev->dev, "MX to APCC APM switch timed out. APCC_APM_CTL_STS=0x%x\n",
++ regval);
++ } else {
++ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
++ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
++ }
++
++ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
++
++ return ret;
++}
++
++static int msm_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ int ret = 0;
++
++ switch (ctrl_dev->msm_id) {
++ case MSM8996_ID:
++ ret = msm8996_apm_switch_to_mx(ctrl_dev);
++ break;
++ case MSM8953_ID:
++ case IPQ807x_ID:
++ ret = msm8953_apm_switch_to_mx(ctrl_dev);
++ break;
++ }
++
++ return ret;
++}
++
++static int msm_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ int ret = 0;
++
++ switch (ctrl_dev->msm_id) {
++ case MSM8996_ID:
++ ret = msm8996_apm_switch_to_apcc(ctrl_dev);
++ break;
++ case MSM8953_ID:
++ case IPQ807x_ID:
++ ret = msm8953_apm_switch_to_apcc(ctrl_dev);
++ break;
++ }
++
++ return ret;
++}
++
++/**
++ * msm_apm_get_supply() - Returns the supply that is currently
++ * powering the memory arrays
++ * @ctrl_dev: Pointer to an MSM APM controller device
++ *
++ * Returns the supply currently selected by the APM.
++ */
++int msm_apm_get_supply(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ return ctrl_dev->supply;
++}
++EXPORT_SYMBOL(msm_apm_get_supply);
++
++/**
++ * msm_apm_set_supply() - Perform the necessary steps to switch the voltage
++ * source of the memory arrays to a given supply
++ * @ctrl_dev: Pointer to an MSM APM controller device
++ * @supply: Power rail to use as supply for the memory
++ * arrays
++ *
++ * Returns 0 on success, -ETIMEDOUT on APM switch timeout, or -EPERM if
++ * the supply is not supported.
++ */
++int msm_apm_set_supply(struct msm_apm_ctrl_dev *ctrl_dev,
++ enum msm_apm_supply supply)
++{
++ int ret;
++
++ switch (supply) {
++ case MSM_APM_SUPPLY_APCC:
++ ret = msm_apm_switch_to_apcc(ctrl_dev);
++ break;
++ case MSM_APM_SUPPLY_MX:
++ ret = msm_apm_switch_to_mx(ctrl_dev);
++ break;
++ default:
++ ret = -EPERM;
++ break;
++ }
++
++ return ret;
++}
++EXPORT_SYMBOL(msm_apm_set_supply);
++
++/**
++ * msm_apm_ctrl_dev_get() - get a handle to the MSM APM controller linked to
++ * the device in device tree
++ * @dev: Pointer to the device
++ *
++ * The device must specify "qcom,apm-ctrl" property in its device tree
++ * node which points to an MSM APM controller device node.
++ *
++ * Returns an MSM APM controller handle if successful or ERR_PTR on any error.
++ * If the APM controller device hasn't probed yet, ERR_PTR(-EPROBE_DEFER) is
++ * returned.
++ */
++struct msm_apm_ctrl_dev *msm_apm_ctrl_dev_get(struct device *dev)
++{
++ struct msm_apm_ctrl_dev *ctrl_dev = NULL;
++ struct msm_apm_ctrl_dev *dev_found = ERR_PTR(-EPROBE_DEFER);
++ struct device_node *ctrl_node;
++
++ if (!dev || !dev->of_node) {
++ pr_err("Invalid device node\n");
++ return ERR_PTR(-EINVAL);
++ }
++
++ ctrl_node = of_parse_phandle(dev->of_node, "qcom,apm-ctrl", 0);
++ if (!ctrl_node) {
++ pr_err("Could not find qcom,apm-ctrl property in %s\n",
++ dev->of_node->full_name);
++ return ERR_PTR(-ENXIO);
++ }
++
++ mutex_lock(&apm_ctrl_list_mutex);
++ list_for_each_entry(ctrl_dev, &apm_ctrl_list, list) {
++ if (ctrl_dev->dev && ctrl_dev->dev->of_node == ctrl_node) {
++ dev_found = ctrl_dev;
++ break;
++ }
++ }
++ mutex_unlock(&apm_ctrl_list_mutex);
++
++ of_node_put(ctrl_node);
++ return dev_found;
++}
++EXPORT_SYMBOL(msm_apm_ctrl_dev_get);
++
++#if defined(CONFIG_DEBUG_FS)
++
++static int apm_supply_dbg_open(struct inode *inode, struct file *filep)
++{
++ filep->private_data = inode->i_private;
++
++ return 0;
++}
++
++static ssize_t apm_supply_dbg_read(struct file *filep, char __user *ubuf,
++ size_t count, loff_t *ppos)
++{
++ struct msm_apm_ctrl_dev *ctrl_dev = filep->private_data;
++ char buf[10];
++ int len;
++
++ if (!ctrl_dev) {
++ pr_err("invalid apm ctrl handle\n");
++ return -ENODEV;
++ }
++
++ if (ctrl_dev->supply == MSM_APM_SUPPLY_APCC)
++ len = snprintf(buf, sizeof(buf), "APCC\n");
++ else if (ctrl_dev->supply == MSM_APM_SUPPLY_MX)
++ len = snprintf(buf, sizeof(buf), "MX\n");
++ else
++ len = snprintf(buf, sizeof(buf), "ERR\n");
++
++ return simple_read_from_buffer(ubuf, count, ppos, buf, len);
++}
++
++static const struct file_operations apm_supply_fops = {
++ .open = apm_supply_dbg_open,
++ .read = apm_supply_dbg_read,
++};
++
++static void apm_debugfs_base_init(void)
++{
++ apm_debugfs_base = debugfs_create_dir("msm-apm", NULL);
++
++ if (IS_ERR_OR_NULL(apm_debugfs_base))
++ pr_err("msm-apm debugfs base directory creation failed\n");
++}
++
++static void apm_debugfs_init(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ struct dentry *temp;
++
++ if (IS_ERR_OR_NULL(apm_debugfs_base)) {
++ pr_err("Base directory missing, cannot create apm debugfs nodes\n");
++ return;
++ }
++
++ ctrl_dev->debugfs = debugfs_create_dir(dev_name(ctrl_dev->dev),
++ apm_debugfs_base);
++ if (IS_ERR_OR_NULL(ctrl_dev->debugfs)) {
++ pr_err("%s debugfs directory creation failed\n",
++ dev_name(ctrl_dev->dev));
++ return;
++ }
++
++ temp = debugfs_create_file("supply", S_IRUGO, ctrl_dev->debugfs,
++ ctrl_dev, &apm_supply_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ pr_err("supply mode creation failed\n");
++ return;
++ }
++}
++
++static void apm_debugfs_deinit(struct msm_apm_ctrl_dev *ctrl_dev)
++{
++ if (!IS_ERR_OR_NULL(ctrl_dev->debugfs))
++ debugfs_remove_recursive(ctrl_dev->debugfs);
++}
++
++static void apm_debugfs_base_remove(void)
++{
++ debugfs_remove_recursive(apm_debugfs_base);
++}
++#else
++
++static void apm_debugfs_base_init(void)
++{}
++
++static void apm_debugfs_init(struct msm_apm_ctrl_dev *ctrl_dev)
++{}
++
++static void apm_debugfs_deinit(struct msm_apm_ctrl_dev *ctrl_dev)
++{}
++
++static void apm_debugfs_base_remove(void)
++{}
++
++#endif
++
++static struct of_device_id msm_apm_match_table[] = {
++ {
++ .compatible = "qcom,msm-apm",
++ .data = (void *)(uintptr_t)MSM8996_ID,
++ },
++ {
++ .compatible = "qcom,msm8953-apm",
++ .data = (void *)(uintptr_t)MSM8953_ID,
++ },
++ {
++ .compatible = "qcom,ipq807x-apm",
++ .data = (void *)(uintptr_t)IPQ807x_ID,
++ },
++ {}
++};
++
++static int msm_apm_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct msm_apm_ctrl_dev *ctrl;
++ const struct of_device_id *match;
++ int ret = 0;
++
++ dev_dbg(dev, "probing MSM Array Power Mux driver\n");
++
++ if (!dev->of_node) {
++ dev_err(dev, "Device tree node is missing\n");
++ return -ENODEV;
++ }
++
++ match = of_match_device(msm_apm_match_table, dev);
++ if (!match)
++ return -ENODEV;
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl) {
++ dev_err(dev, "MSM APM controller memory allocation failed\n");
++ return -ENOMEM;
++ }
++
++ INIT_LIST_HEAD(&ctrl->list);
++ spin_lock_init(&ctrl->lock);
++ ctrl->dev = dev;
++ ctrl->msm_id = (uintptr_t)match->data;
++ platform_set_drvdata(pdev, ctrl);
++
++ switch (ctrl->msm_id) {
++ case MSM8996_ID:
++ ret = msm_apm_ctrl_devm_ioremap(pdev, ctrl);
++ if (ret) {
++ dev_err(dev, "Failed to add APM controller device\n");
++ return ret;
++ }
++ break;
++ case MSM8953_ID:
++ case IPQ807x_ID:
++ ret = msm8953_apm_ctrl_init(pdev, ctrl);
++ if (ret) {
++ dev_err(dev, "Failed to initialize APM controller device: ret=%d\n",
++ ret);
++ return ret;
++ }
++ break;
++ default:
++ dev_err(dev, "unable to add APM controller device for msm_id:%d\n",
++ ctrl->msm_id);
++ return -ENODEV;
++ }
++
++ apm_debugfs_init(ctrl);
++ mutex_lock(&apm_ctrl_list_mutex);
++ list_add_tail(&ctrl->list, &apm_ctrl_list);
++ mutex_unlock(&apm_ctrl_list_mutex);
++
++ dev_dbg(dev, "MSM Array Power Mux driver probe successful");
++
++ return ret;
++}
++
++static int msm_apm_remove(struct platform_device *pdev)
++{
++ struct msm_apm_ctrl_dev *ctrl_dev;
++
++ ctrl_dev = platform_get_drvdata(pdev);
++ if (ctrl_dev) {
++ mutex_lock(&apm_ctrl_list_mutex);
++ list_del(&ctrl_dev->list);
++ mutex_unlock(&apm_ctrl_list_mutex);
++ apm_debugfs_deinit(ctrl_dev);
++ }
++
++ return 0;
++}
++
++static struct platform_driver msm_apm_driver = {
++ .driver = {
++ .name = MSM_APM_DRIVER_NAME,
++ .of_match_table = msm_apm_match_table,
++ .owner = THIS_MODULE,
++ },
++ .probe = msm_apm_probe,
++ .remove = msm_apm_remove,
++};
++
++static int __init msm_apm_init(void)
++{
++ apm_debugfs_base_init();
++ return platform_driver_register(&msm_apm_driver);
++}
++
++static void __exit msm_apm_exit(void)
++{
++ platform_driver_unregister(&msm_apm_driver);
++ apm_debugfs_base_remove();
++}
++
++arch_initcall(msm_apm_init);
++module_exit(msm_apm_exit);
++
++MODULE_DESCRIPTION("MSM Array Power Mux driver");
++MODULE_LICENSE("GPL v2");
+--- /dev/null
++++ b/include/linux/power/qcom/apm.h
+@@ -0,0 +1,48 @@
++/*
++ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef __LINUX_POWER_QCOM_APM_H__
++#define __LINUX_POWER_QCOM_APM_H__
++
++#include <linux/device.h>
++#include <linux/err.h>
++
++/**
++ * enum msm_apm_supply - supported power rails to supply memory arrays
++ * %MSM_APM_SUPPLY_APCC: to enable selection of VDD_APCC rail as supply
++ * %MSM_APM_SUPPLY_MX: to enable selection of VDD_MX rail as supply
++ */
++enum msm_apm_supply {
++ MSM_APM_SUPPLY_APCC,
++ MSM_APM_SUPPLY_MX,
++};
++
++/* Handle used to identify an APM controller device */
++struct msm_apm_ctrl_dev;
++
++#ifdef CONFIG_QCOM_APM
++struct msm_apm_ctrl_dev *msm_apm_ctrl_dev_get(struct device *dev);
++int msm_apm_set_supply(struct msm_apm_ctrl_dev *ctrl_dev,
++ enum msm_apm_supply supply);
++int msm_apm_get_supply(struct msm_apm_ctrl_dev *ctrl_dev);
++
++#else
++static inline struct msm_apm_ctrl_dev *msm_apm_ctrl_dev_get(struct device *dev)
++{ return ERR_PTR(-EPERM); }
++static inline int msm_apm_set_supply(struct msm_apm_ctrl_dev *ctrl_dev,
++ enum msm_apm_supply supply)
++{ return -EPERM; }
++static inline int msm_apm_get_supply(struct msm_apm_ctrl_dev *ctrl_dev)
++{ return -EPERM; }
++#endif
++#endif
diff --git a/target/linux/qualcommax/patches-6.1/0901-regulator-add-Qualcomm-CPR-regulators.patch b/target/linux/qualcommax/patches-6.1/0901-regulator-add-Qualcomm-CPR-regulators.patch
new file mode 100644
index 0000000000..9b9f7159f3
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0901-regulator-add-Qualcomm-CPR-regulators.patch
@@ -0,0 +1,12144 @@
+From c9df32c057e43e38c8113199e64f7a64f8d341df Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Mon, 11 Apr 2022 14:35:36 +0200
+Subject: [PATCH] regulator: add Qualcomm CPR regulators
+
+Allow building Qualcomm CPR regulators.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/regulator/Kconfig | 33 +
+ drivers/regulator/Makefile | 3 +
+ drivers/regulator/cpr3-npu-regulator.c | 695 +++
+ drivers/regulator/cpr3-regulator.c | 5111 +++++++++++++++++++++++
+ drivers/regulator/cpr3-regulator.h | 1211 ++++++
+ drivers/regulator/cpr3-util.c | 2750 ++++++++++++
+ drivers/regulator/cpr4-apss-regulator.c | 1819 ++++++++
+ include/soc/qcom/socinfo.h | 463 ++
+ 8 files changed, 12085 insertions(+)
+ create mode 100644 drivers/regulator/cpr3-npu-regulator.c
+ create mode 100644 drivers/regulator/cpr3-regulator.c
+ create mode 100644 drivers/regulator/cpr3-regulator.h
+ create mode 100644 drivers/regulator/cpr3-util.c
+ create mode 100644 drivers/regulator/cpr4-apss-regulator.c
+ create mode 100644 include/soc/qcom/socinfo.h
+
+--- a/drivers/regulator/Kconfig
++++ b/drivers/regulator/Kconfig
+@@ -1524,4 +1524,37 @@ config REGULATOR_QCOM_LABIBB
+ boost regulator and IBB can be used as a negative boost regulator
+ for LCD display panel.
+
++config REGULATOR_CPR3
++ bool "QCOM CPR3 regulator core support"
++ help
++ This driver supports Core Power Reduction (CPR) version 3 controllers
++ which are used by some Qualcomm Technologies, Inc. SoCs to
++ manage important voltage regulators. CPR3 controllers are capable of
++ monitoring several ring oscillator sensing loops simultaneously. The
++ CPR3 controller informs software when the silicon conditions require
++ the supply voltage to be increased or decreased. On certain supply
++ rails, the CPR3 controller is able to propagate the voltage increase
++ or decrease requests all the way to the PMIC without software
++ involvement.
++
++config REGULATOR_CPR3_NPU
++ bool "QCOM CPR3 regulator for NPU"
++ depends on OF && REGULATOR_CPR3
++ help
++ This driver supports Qualcomm Technologies, Inc. NPU CPR3
++ regulator Which will always operate in open loop.
++
++config REGULATOR_CPR4_APSS
++ bool "QCOM CPR4 regulator for APSS"
++ depends on OF && REGULATOR_CPR3
++ help
++ This driver supports Qualcomm Technologies, Inc. APSS application
++ processor specific features including memory array power mux (APM)
++ switching, one CPR4 thread which monitor the two APSS clusters that
++ are both powered by a shared supply, hardware closed-loop auto
++ voltage stepping, voltage adjustments based on online core count,
++ voltage adjustments based on temperature readings, and voltage
++ adjustments for performance boost mode. This driver reads both initial
++ voltage and CPR target quotient values out of hardware fuses.
++
+ endif
+--- a/drivers/regulator/Makefile
++++ b/drivers/regulator/Makefile
+@@ -110,6 +110,9 @@ obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qco
+ obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
+ obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
+ obj-$(CONFIG_REGULATOR_QCOM_USB_VBUS) += qcom_usb_vbus-regulator.o
++obj-$(CONFIG_REGULATOR_CPR3) += cpr3-regulator.o cpr3-util.o
++obj-$(CONFIG_REGULATOR_CPR3_NPU) += cpr3-npu-regulator.o
++obj-$(CONFIG_REGULATOR_CPR4_APSS) += cpr4-apss-regulator.o
+ obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
+ obj-$(CONFIG_REGULATOR_PCA9450) += pca9450-regulator.o
+ obj-$(CONFIG_REGULATOR_PF8X00) += pf8x00-regulator.o
+--- /dev/null
++++ b/drivers/regulator/cpr3-npu-regulator.c
+@@ -0,0 +1,695 @@
++/*
++ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
++ *
++ * Permission to use, copy, modify, and/or distribute this software for any
++ * purpose with or without fee is hereby granted, provided that the above
++ * copyright notice and this permission notice appear in all copies.
++ *
++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
++ */
++
++#include <linux/err.h>
++#include <linux/platform_device.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/of_device.h>
++#include <linux/slab.h>
++#include <linux/thermal.h>
++
++#include "cpr3-regulator.h"
++
++#define IPQ807x_NPU_FUSE_CORNERS 2
++#define IPQ817x_NPU_FUSE_CORNERS 1
++#define IPQ807x_NPU_FUSE_STEP_VOLT 8000
++#define IPQ807x_NPU_VOLTAGE_FUSE_SIZE 6
++#define IPQ807x_NPU_CPR_CLOCK_RATE 19200000
++
++#define IPQ807x_NPU_CPR_TCSR_START 6
++#define IPQ807x_NPU_CPR_TCSR_END 7
++
++#define NPU_TSENS 5
++
++u32 g_valid_npu_fuse_count = IPQ807x_NPU_FUSE_CORNERS;
++/**
++ * struct cpr3_ipq807x_npu_fuses - NPU specific fuse data for IPQ807x
++ * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
++ * for each fuse corner (raw, not converted to a voltage)
++ * This struct holds the values for all of the fuses read from memory.
++ */
++struct cpr3_ipq807x_npu_fuses {
++ u64 init_voltage[IPQ807x_NPU_FUSE_CORNERS];
++};
++
++/*
++ * Constants which define the name of each fuse corner.
++ */
++enum cpr3_ipq807x_npu_fuse_corner {
++ CPR3_IPQ807x_NPU_FUSE_CORNER_NOM = 0,
++ CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO = 1,
++};
++
++static const char * const cpr3_ipq807x_npu_fuse_corner_name[] = {
++ [CPR3_IPQ807x_NPU_FUSE_CORNER_NOM] = "NOM",
++ [CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO] = "TURBO",
++};
++
++/*
++ * IPQ807x NPU fuse parameter locations:
++ *
++ * Structs are organized with the following dimensions:
++ * Outer: 0 to 1 for fuse corners from lowest to highest corner
++ * Inner: large enough to hold the longest set of parameter segments which
++ * fully defines a fuse parameter, +1 (for NULL termination).
++ * Each segment corresponds to a contiguous group of bits from a
++ * single fuse row. These segments are concatentated together in
++ * order to form the full fuse parameter value. The segments for
++ * a given parameter may correspond to different fuse rows.
++ */
++static struct cpr3_fuse_param
++ipq807x_npu_init_voltage_param[IPQ807x_NPU_FUSE_CORNERS][2] = {
++ {{73, 22, 27}, {} },
++ {{73, 16, 21}, {} },
++};
++
++/*
++ * Open loop voltage fuse reference voltages in microvolts for IPQ807x
++ */
++static int
++ipq807x_npu_fuse_ref_volt [IPQ807x_NPU_FUSE_CORNERS] = {
++ 912000,
++ 992000,
++};
++
++/*
++ * IPQ9574 (Few parameters are changed, remaining are same as IPQ807x)
++ */
++#define IPQ9574_NPU_FUSE_CORNERS 2
++#define IPQ9574_NPU_FUSE_STEP_VOLT 10000
++#define IPQ9574_NPU_CPR_CLOCK_RATE 24000000
++
++/*
++ * fues parameters for IPQ9574
++ */
++static struct cpr3_fuse_param
++ipq9574_npu_init_voltage_param[IPQ9574_NPU_FUSE_CORNERS][2] = {
++ {{105, 12, 17}, {} },
++ {{105, 6, 11}, {} },
++};
++
++/*
++ * Open loop voltage fuse reference voltages in microvolts for IPQ9574
++ */
++static int
++ipq9574_npu_fuse_ref_volt [IPQ9574_NPU_FUSE_CORNERS] = {
++ 862500,
++ 987500,
++};
++
++struct cpr3_controller *g_ctrl;
++
++void cpr3_npu_temp_notify(int sensor, int temp, int low_notif)
++{
++ u32 prev_sensor_state;
++
++ if (sensor != NPU_TSENS)
++ return;
++
++ prev_sensor_state = g_ctrl->cur_sensor_state;
++ if (low_notif)
++ g_ctrl->cur_sensor_state |= BIT(sensor);
++ else
++ g_ctrl->cur_sensor_state &= ~BIT(sensor);
++
++ if (!prev_sensor_state && g_ctrl->cur_sensor_state)
++ cpr3_handle_temp_open_loop_adjustment(g_ctrl, true);
++ else if (prev_sensor_state && !g_ctrl->cur_sensor_state)
++ cpr3_handle_temp_open_loop_adjustment(g_ctrl, false);
++}
++
++/**
++ * cpr3_ipq807x_npu_read_fuse_data() - load NPU specific fuse parameter values
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function allocates a cpr3_ipq807x_npu_fuses struct, fills it with
++ * values read out of hardware fuses, and finally copies common fuse values
++ * into the CPR3 regulator struct.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_ipq807x_npu_read_fuse_data(struct cpr3_regulator *vreg)
++{
++ void __iomem *base = vreg->thread->ctrl->fuse_base;
++ struct cpr3_ipq807x_npu_fuses *fuse;
++ int i, rc;
++
++ fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
++ if (!fuse)
++ return -ENOMEM;
++
++ for (i = 0; i < g_valid_npu_fuse_count; i++) {
++ rc = cpr3_read_fuse_param(base,
++ vreg->cpr3_regulator_data->init_voltage_param[i],
++ &fuse->init_voltage[i]);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
++ i, rc);
++ return rc;
++ }
++ }
++
++ vreg->fuse_corner_count = g_valid_npu_fuse_count;
++ vreg->platform_fuses = fuse;
++
++ return 0;
++}
++
++/**
++ * cpr3_npu_parse_corner_data() - parse NPU corner data from device tree
++ * properties of the CPR3 regulator's device node
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_npu_parse_corner_data(struct cpr3_regulator *vreg)
++{
++ int rc;
++
++ rc = cpr3_parse_common_corner_data(vreg);
++ if (rc) {
++ cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
++ return rc;
++ }
++
++ return rc;
++}
++
++/**
++ * cpr3_ipq807x_npu_calculate_open_loop_voltages() - calculate the open-loop
++ * voltage for each corner of a CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ * @temp_correction: Temperature based correction
++ *
++ * If open-loop voltage interpolation is allowed in device tree, then
++ * this function calculates the open-loop voltage for a given corner using
++ * linear interpolation. This interpolation is performed using the processor
++ * frequencies of the lower and higher Fmax corners along with their fused
++ * open-loop voltages.
++ *
++ * If open-loop voltage interpolation is not allowed, then this function uses
++ * the Fmax fused open-loop voltage for all of the corners associated with a
++ * given fuse corner.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_ipq807x_npu_calculate_open_loop_voltages(
++ struct cpr3_regulator *vreg, bool temp_correction)
++{
++ struct cpr3_ipq807x_npu_fuses *fuse = vreg->platform_fuses;
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ int i, j, rc = 0;
++ u64 freq_low, volt_low, freq_high, volt_high;
++ int *fuse_volt;
++ int *fmax_corner;
++
++ fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
++ GFP_KERNEL);
++ fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
++ GFP_KERNEL);
++ if (!fuse_volt || !fmax_corner) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ for (i = 0; i < vreg->fuse_corner_count; i++) {
++ if (ctrl->cpr_global_setting == CPR_DISABLED)
++ fuse_volt[i] = vreg->cpr3_regulator_data->fuse_ref_volt[i];
++ else
++ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
++ vreg->cpr3_regulator_data->fuse_ref_volt[i],
++ vreg->cpr3_regulator_data->fuse_step_volt,
++ fuse->init_voltage[i],
++ IPQ807x_NPU_VOLTAGE_FUSE_SIZE);
++
++ /* Log fused open-loop voltage values for debugging purposes. */
++ cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
++ cpr3_ipq807x_npu_fuse_corner_name[i],
++ fuse_volt[i]);
++ }
++
++ rc = cpr3_determine_part_type(vreg,
++ fuse_volt[CPR3_IPQ807x_NPU_FUSE_CORNER_TURBO]);
++ if (rc) {
++ cpr3_err(vreg,
++ "fused part type detection failed failed, rc=%d\n", rc);
++ goto done;
++ }
++
++ rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
++ if (rc) {
++ cpr3_err(vreg,
++ "fused open-loop voltage adjustment failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ if (temp_correction) {
++ rc = cpr3_determine_temp_base_open_loop_correction(vreg,
++ fuse_volt);
++ if (rc) {
++ cpr3_err(vreg,
++ "temp open-loop voltage adj. failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ for (i = 1; i < vreg->fuse_corner_count; i++) {
++ if (fuse_volt[i] < fuse_volt[i - 1]) {
++ cpr3_info(vreg,
++ "fuse corner %d voltage=%d uV < fuse corner %d \
++ voltage=%d uV; overriding: fuse corner %d \
++ voltage=%d\n",
++ i, fuse_volt[i], i - 1, fuse_volt[i - 1],
++ i, fuse_volt[i - 1]);
++ fuse_volt[i] = fuse_volt[i - 1];
++ }
++ }
++
++ /* Determine highest corner mapped to each fuse corner */
++ j = vreg->fuse_corner_count - 1;
++ for (i = vreg->corner_count - 1; i >= 0; i--) {
++ if (vreg->corner[i].cpr_fuse_corner == j) {
++ fmax_corner[j] = i;
++ j--;
++ }
++ }
++
++ if (j >= 0) {
++ cpr3_err(vreg, "invalid fuse corner mapping\n");
++ rc = -EINVAL;
++ goto done;
++ }
++
++ /*
++ * Interpolation is not possible for corners mapped to the lowest fuse
++ * corner so use the fuse corner value directly.
++ */
++ for (i = 0; i <= fmax_corner[0]; i++)
++ vreg->corner[i].open_loop_volt = fuse_volt[0];
++
++ /* Interpolate voltages for the higher fuse corners. */
++ for (i = 1; i < vreg->fuse_corner_count; i++) {
++ freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
++ volt_low = fuse_volt[i - 1];
++ freq_high = vreg->corner[fmax_corner[i]].proc_freq;
++ volt_high = fuse_volt[i];
++
++ for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
++ vreg->corner[j].open_loop_volt = cpr3_interpolate(
++ freq_low, volt_low, freq_high, volt_high,
++ vreg->corner[j].proc_freq);
++ }
++
++done:
++ if (rc == 0) {
++ cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
++ for (i = 0; i < vreg->corner_count; i++)
++ cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
++ vreg->corner[i].open_loop_volt);
++
++ rc = cpr3_adjust_open_loop_voltages(vreg);
++ if (rc)
++ cpr3_err(vreg,
++ "open-loop voltage adjustment failed, rc=%d\n",
++ rc);
++ }
++
++ kfree(fuse_volt);
++ kfree(fmax_corner);
++ return rc;
++}
++
++/**
++ * cpr3_npu_print_settings() - print out NPU CPR configuration settings into
++ * the kernel log for debugging purposes
++ * @vreg: Pointer to the CPR3 regulator
++ */
++static void cpr3_npu_print_settings(struct cpr3_regulator *vreg)
++{
++ struct cpr3_corner *corner;
++ int i;
++
++ cpr3_debug(vreg,
++ "Corner: Frequency (Hz), Fuse Corner, Floor (uV), \
++ Open-Loop (uV), Ceiling (uV)\n");
++ for (i = 0; i < vreg->corner_count; i++) {
++ corner = &vreg->corner[i];
++ cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
++ i, corner->proc_freq, corner->cpr_fuse_corner,
++ corner->floor_volt, corner->open_loop_volt,
++ corner->ceiling_volt);
++ }
++
++ if (vreg->thread->ctrl->apm)
++ cpr3_debug(vreg, "APM threshold = %d uV, APM adjust = %d uV\n",
++ vreg->thread->ctrl->apm_threshold_volt,
++ vreg->thread->ctrl->apm_adj_volt);
++}
++
++/**
++ * cpr3_ipq807x_npu_calc_temp_based_ol_voltages() - Calculate the open loop
++ * voltages based on temperature based correction margins
++ * @vreg: Pointer to the CPR3 regulator
++ */
++
++static int
++cpr3_ipq807x_npu_calc_temp_based_ol_voltages(struct cpr3_regulator *vreg,
++ bool temp_correction)
++{
++ int rc, i;
++
++ rc = cpr3_ipq807x_npu_calculate_open_loop_voltages(vreg,
++ temp_correction);
++ if (rc) {
++ cpr3_err(vreg,
++ "unable to calculate open-loop voltages, rc=%d\n", rc);
++ return rc;
++ }
++
++ rc = cpr3_limit_open_loop_voltages(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ cpr3_open_loop_voltage_as_ceiling(vreg);
++
++ rc = cpr3_limit_floor_voltages(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
++ return rc;
++ }
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ if (temp_correction)
++ vreg->corner[i].cold_temp_open_loop_volt =
++ vreg->corner[i].open_loop_volt;
++ else
++ vreg->corner[i].normal_temp_open_loop_volt =
++ vreg->corner[i].open_loop_volt;
++ }
++
++ cpr3_npu_print_settings(vreg);
++
++ return rc;
++}
++
++/**
++ * cpr3_npu_init_thread() - perform steps necessary to initialize the
++ * configuration data for a CPR3 thread
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_npu_init_thread(struct cpr3_thread *thread)
++{
++ int rc;
++
++ rc = cpr3_parse_common_thread_data(thread);
++ if (rc) {
++ cpr3_err(thread->ctrl,
++ "thread %u CPR thread data from DT- failed, rc=%d\n",
++ thread->thread_id, rc);
++ return rc;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_npu_init_regulator() - perform all steps necessary to initialize the
++ * configuration data for a CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_npu_init_regulator(struct cpr3_regulator *vreg)
++{
++ struct cpr3_ipq807x_npu_fuses *fuse;
++ int rc, cold_temp = 0;
++ bool can_adj_cold_temp = cpr3_can_adjust_cold_temp(vreg);
++
++ rc = cpr3_ipq807x_npu_read_fuse_data(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
++ return rc;
++ }
++
++ fuse = vreg->platform_fuses;
++
++ rc = cpr3_npu_parse_corner_data(vreg);
++ if (rc) {
++ cpr3_err(vreg,
++ "Cannot read CPR corner data from DT, rc=%d\n", rc);
++ return rc;
++ }
++
++ rc = cpr3_mem_acc_init(vreg);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(vreg,
++ "Cannot initialize mem-acc regulator settings, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (can_adj_cold_temp) {
++ rc = cpr3_ipq807x_npu_calc_temp_based_ol_voltages(vreg, true);
++ if (rc) {
++ cpr3_err(vreg,
++ "unable to calculate open-loop voltages, rc=%d\n", rc);
++ return rc;
++ }
++ }
++
++ rc = cpr3_ipq807x_npu_calc_temp_based_ol_voltages(vreg, false);
++ if (rc) {
++ cpr3_err(vreg,
++ "unable to calculate open-loop voltages, rc=%d\n", rc);
++ return rc;
++ }
++
++ if (can_adj_cold_temp) {
++ cpr3_info(vreg,
++ "Normal and Cold condition init done. Default to normal.\n");
++
++ rc = cpr3_get_cold_temp_threshold(vreg, &cold_temp);
++ if (rc) {
++ cpr3_err(vreg,
++ "Get cold temperature threshold failed, rc=%d\n", rc);
++ return rc;
++ }
++ register_low_temp_notif(NPU_TSENS, cold_temp,
++ cpr3_npu_temp_notify);
++ }
++
++ return rc;
++}
++
++/**
++ * cpr3_npu_init_controller() - perform NPU CPR3 controller specific
++ * initializations
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_npu_init_controller(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ rc = cpr3_parse_open_loop_common_ctrl_data(ctrl);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ ctrl->ctrl_type = CPR_CTRL_TYPE_CPR3;
++ ctrl->supports_hw_closed_loop = false;
++
++ return 0;
++}
++
++static const struct cpr3_reg_data ipq807x_cpr_npu = {
++ .cpr_valid_fuse_count = IPQ807x_NPU_FUSE_CORNERS,
++ .init_voltage_param = ipq807x_npu_init_voltage_param,
++ .fuse_ref_volt = ipq807x_npu_fuse_ref_volt,
++ .fuse_step_volt = IPQ807x_NPU_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ807x_NPU_CPR_CLOCK_RATE,
++};
++
++static const struct cpr3_reg_data ipq817x_cpr_npu = {
++ .cpr_valid_fuse_count = IPQ817x_NPU_FUSE_CORNERS,
++ .init_voltage_param = ipq807x_npu_init_voltage_param,
++ .fuse_ref_volt = ipq807x_npu_fuse_ref_volt,
++ .fuse_step_volt = IPQ807x_NPU_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ807x_NPU_CPR_CLOCK_RATE,
++};
++
++static const struct cpr3_reg_data ipq9574_cpr_npu = {
++ .cpr_valid_fuse_count = IPQ9574_NPU_FUSE_CORNERS,
++ .init_voltage_param = ipq9574_npu_init_voltage_param,
++ .fuse_ref_volt = ipq9574_npu_fuse_ref_volt,
++ .fuse_step_volt = IPQ9574_NPU_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ9574_NPU_CPR_CLOCK_RATE,
++};
++
++static struct of_device_id cpr3_regulator_match_table[] = {
++ {
++ .compatible = "qcom,cpr3-ipq807x-npu-regulator",
++ .data = &ipq807x_cpr_npu
++ },
++ {
++ .compatible = "qcom,cpr3-ipq817x-npu-regulator",
++ .data = &ipq817x_cpr_npu
++ },
++ {
++ .compatible = "qcom,cpr3-ipq9574-npu-regulator",
++ .data = &ipq9574_cpr_npu
++ },
++ {}
++};
++
++static int cpr3_npu_regulator_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct cpr3_controller *ctrl;
++ int i, rc;
++ const struct of_device_id *match;
++ struct cpr3_reg_data *cpr_data;
++
++ if (!dev->of_node) {
++ dev_err(dev, "Device tree node is missing\n");
++ return -EINVAL;
++ }
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl)
++ return -ENOMEM;
++ g_ctrl = ctrl;
++
++ match = of_match_device(cpr3_regulator_match_table, &pdev->dev);
++ if (!match)
++ return -ENODEV;
++
++ cpr_data = (struct cpr3_reg_data *)match->data;
++ g_valid_npu_fuse_count = cpr_data->cpr_valid_fuse_count;
++ dev_info(dev, "NPU CPR valid fuse count: %d\n", g_valid_npu_fuse_count);
++ ctrl->cpr_clock_rate = cpr_data->cpr_clk_rate;
++
++ ctrl->dev = dev;
++ /* Set to false later if anything precludes CPR operation. */
++ ctrl->cpr_allowed_hw = true;
++
++ rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
++ &ctrl->name);
++ if (rc) {
++ cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr3_map_fuse_base(ctrl, pdev);
++ if (rc) {
++ cpr3_err(ctrl, "could not map fuse base address\n");
++ return rc;
++ }
++
++ rc = cpr3_read_tcsr_setting(ctrl, pdev, IPQ807x_NPU_CPR_TCSR_START,
++ IPQ807x_NPU_CPR_TCSR_END);
++ if (rc) {
++ cpr3_err(ctrl, "could not read CPR tcsr rsetting\n");
++ return rc;
++ }
++
++ rc = cpr3_allocate_threads(ctrl, 0, 0);
++ if (rc) {
++ cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (ctrl->thread_count != 1) {
++ cpr3_err(ctrl, "expected 1 thread but found %d\n",
++ ctrl->thread_count);
++ return -EINVAL;
++ }
++
++ rc = cpr3_npu_init_controller(ctrl);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr3_npu_init_thread(&ctrl->thread[0]);
++ if (rc) {
++ cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ for (i = 0; i < ctrl->thread[0].vreg_count; i++) {
++ ctrl->thread[0].vreg[i].cpr3_regulator_data = cpr_data;
++ rc = cpr3_npu_init_regulator(&ctrl->thread[0].vreg[i]);
++ if (rc) {
++ cpr3_err(&ctrl->thread[0].vreg[i], "regulator initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ platform_set_drvdata(pdev, ctrl);
++
++ return cpr3_open_loop_regulator_register(pdev, ctrl);
++}
++
++static int cpr3_npu_regulator_remove(struct platform_device *pdev)
++{
++ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
++
++ return cpr3_open_loop_regulator_unregister(ctrl);
++}
++
++static struct platform_driver cpr3_npu_regulator_driver = {
++ .driver = {
++ .name = "qcom,cpr3-npu-regulator",
++ .of_match_table = cpr3_regulator_match_table,
++ .owner = THIS_MODULE,
++ },
++ .probe = cpr3_npu_regulator_probe,
++ .remove = cpr3_npu_regulator_remove,
++};
++
++static int cpr3_regulator_init(void)
++{
++ return platform_driver_register(&cpr3_npu_regulator_driver);
++}
++arch_initcall(cpr3_regulator_init);
++
++static void cpr3_regulator_exit(void)
++{
++ platform_driver_unregister(&cpr3_npu_regulator_driver);
++}
++module_exit(cpr3_regulator_exit);
++
++MODULE_DESCRIPTION("QCOM CPR3 NPU regulator driver");
++MODULE_LICENSE("Dual BSD/GPLv2");
++MODULE_ALIAS("platform:npu-ipq807x");
+--- /dev/null
++++ b/drivers/regulator/cpr3-regulator.c
+@@ -0,0 +1,5111 @@
++/*
++ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#define pr_fmt(fmt) "%s: " fmt, __func__
++
++#include <linux/bitops.h>
++#include <linux/debugfs.h>
++#include <linux/delay.h>
++#include <linux/err.h>
++#include <linux/init.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/ktime.h>
++#include <linux/list.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/of_device.h>
++#include <linux/platform_device.h>
++#include <linux/pm_opp.h>
++#include <linux/slab.h>
++#include <linux/sort.h>
++#include <linux/string.h>
++#include <linux/uaccess.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/machine.h>
++#include <linux/regulator/of_regulator.h>
++#include <linux/panic_notifier.h>
++
++#include "cpr3-regulator.h"
++
++#define CPR3_REGULATOR_CORNER_INVALID (-1)
++#define CPR3_RO_MASK GENMASK(CPR3_RO_COUNT - 1, 0)
++
++/* CPR3 registers */
++#define CPR3_REG_CPR_CTL 0x4
++#define CPR3_CPR_CTL_LOOP_EN_MASK BIT(0)
++#define CPR3_CPR_CTL_LOOP_ENABLE BIT(0)
++#define CPR3_CPR_CTL_LOOP_DISABLE 0
++#define CPR3_CPR_CTL_IDLE_CLOCKS_MASK GENMASK(5, 1)
++#define CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT 1
++#define CPR3_CPR_CTL_COUNT_MODE_MASK GENMASK(7, 6)
++#define CPR3_CPR_CTL_COUNT_MODE_SHIFT 6
++#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN 0
++#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MAX 1
++#define CPR3_CPR_CTL_COUNT_MODE_STAGGERED 2
++#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE 3
++#define CPR3_CPR_CTL_COUNT_REPEAT_MASK GENMASK(31, 9)
++#define CPR3_CPR_CTL_COUNT_REPEAT_SHIFT 9
++
++#define CPR3_REG_CPR_STATUS 0x8
++#define CPR3_CPR_STATUS_BUSY_MASK BIT(0)
++#define CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK BIT(1)
++
++/*
++ * This register is not present on controllers that support HW closed-loop
++ * except CPR4 APSS controller.
++ */
++#define CPR3_REG_CPR_TIMER_AUTO_CONT 0xC
++
++#define CPR3_REG_CPR_STEP_QUOT 0x14
++#define CPR3_CPR_STEP_QUOT_MIN_MASK GENMASK(5, 0)
++#define CPR3_CPR_STEP_QUOT_MIN_SHIFT 0
++#define CPR3_CPR_STEP_QUOT_MAX_MASK GENMASK(11, 6)
++#define CPR3_CPR_STEP_QUOT_MAX_SHIFT 6
++
++#define CPR3_REG_GCNT(ro) (0xA0 + 0x4 * (ro))
++
++#define CPR3_REG_SENSOR_BYPASS_WRITE(sensor) (0xE0 + 0x4 * ((sensor) / 32))
++#define CPR3_REG_SENSOR_BYPASS_WRITE_BANK(bank) (0xE0 + 0x4 * (bank))
++
++#define CPR3_REG_SENSOR_MASK_WRITE(sensor) (0x120 + 0x4 * ((sensor) / 32))
++#define CPR3_REG_SENSOR_MASK_WRITE_BANK(bank) (0x120 + 0x4 * (bank))
++#define CPR3_REG_SENSOR_MASK_READ(sensor) (0x140 + 0x4 * ((sensor) / 32))
++
++#define CPR3_REG_SENSOR_OWNER(sensor) (0x200 + 0x4 * (sensor))
++
++#define CPR3_REG_CONT_CMD 0x800
++#define CPR3_CONT_CMD_ACK 0x1
++#define CPR3_CONT_CMD_NACK 0x0
++
++#define CPR3_REG_THRESH(thread) (0x808 + 0x440 * (thread))
++#define CPR3_THRESH_CONS_DOWN_MASK GENMASK(3, 0)
++#define CPR3_THRESH_CONS_DOWN_SHIFT 0
++#define CPR3_THRESH_CONS_UP_MASK GENMASK(7, 4)
++#define CPR3_THRESH_CONS_UP_SHIFT 4
++#define CPR3_THRESH_DOWN_THRESH_MASK GENMASK(12, 8)
++#define CPR3_THRESH_DOWN_THRESH_SHIFT 8
++#define CPR3_THRESH_UP_THRESH_MASK GENMASK(17, 13)
++#define CPR3_THRESH_UP_THRESH_SHIFT 13
++
++#define CPR3_REG_RO_MASK(thread) (0x80C + 0x440 * (thread))
++
++#define CPR3_REG_RESULT0(thread) (0x810 + 0x440 * (thread))
++#define CPR3_RESULT0_BUSY_MASK BIT(0)
++#define CPR3_RESULT0_STEP_DN_MASK BIT(1)
++#define CPR3_RESULT0_STEP_UP_MASK BIT(2)
++#define CPR3_RESULT0_ERROR_STEPS_MASK GENMASK(7, 3)
++#define CPR3_RESULT0_ERROR_STEPS_SHIFT 3
++#define CPR3_RESULT0_ERROR_MASK GENMASK(19, 8)
++#define CPR3_RESULT0_ERROR_SHIFT 8
++#define CPR3_RESULT0_NEGATIVE_MASK BIT(20)
++
++#define CPR3_REG_RESULT1(thread) (0x814 + 0x440 * (thread))
++#define CPR3_RESULT1_QUOT_MIN_MASK GENMASK(11, 0)
++#define CPR3_RESULT1_QUOT_MIN_SHIFT 0
++#define CPR3_RESULT1_QUOT_MAX_MASK GENMASK(23, 12)
++#define CPR3_RESULT1_QUOT_MAX_SHIFT 12
++#define CPR3_RESULT1_RO_MIN_MASK GENMASK(27, 24)
++#define CPR3_RESULT1_RO_MIN_SHIFT 24
++#define CPR3_RESULT1_RO_MAX_MASK GENMASK(31, 28)
++#define CPR3_RESULT1_RO_MAX_SHIFT 28
++
++#define CPR3_REG_RESULT2(thread) (0x818 + 0x440 * (thread))
++#define CPR3_RESULT2_STEP_QUOT_MIN_MASK GENMASK(5, 0)
++#define CPR3_RESULT2_STEP_QUOT_MIN_SHIFT 0
++#define CPR3_RESULT2_STEP_QUOT_MAX_MASK GENMASK(11, 6)
++#define CPR3_RESULT2_STEP_QUOT_MAX_SHIFT 6
++#define CPR3_RESULT2_SENSOR_MIN_MASK GENMASK(23, 16)
++#define CPR3_RESULT2_SENSOR_MIN_SHIFT 16
++#define CPR3_RESULT2_SENSOR_MAX_MASK GENMASK(31, 24)
++#define CPR3_RESULT2_SENSOR_MAX_SHIFT 24
++
++#define CPR3_REG_IRQ_EN 0x81C
++#define CPR3_REG_IRQ_CLEAR 0x820
++#define CPR3_REG_IRQ_STATUS 0x824
++#define CPR3_IRQ_UP BIT(3)
++#define CPR3_IRQ_MID BIT(2)
++#define CPR3_IRQ_DOWN BIT(1)
++
++#define CPR3_REG_TARGET_QUOT(thread, ro) \
++ (0x840 + 0x440 * (thread) + 0x4 * (ro))
++
++/* Registers found only on controllers that support HW closed-loop. */
++#define CPR3_REG_PD_THROTTLE 0xE8
++#define CPR3_PD_THROTTLE_DISABLE 0x0
++
++#define CPR3_REG_HW_CLOSED_LOOP 0x3000
++#define CPR3_HW_CLOSED_LOOP_ENABLE 0x0
++#define CPR3_HW_CLOSED_LOOP_DISABLE 0x1
++
++#define CPR3_REG_CPR_TIMER_MID_CONT 0x3004
++#define CPR3_REG_CPR_TIMER_UP_DN_CONT 0x3008
++
++#define CPR3_REG_LAST_MEASUREMENT 0x7F8
++#define CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT 0
++#define CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT 4
++#define CPR3_LAST_MEASUREMENT_THREAD_DN(thread) \
++ (BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT)
++#define CPR3_LAST_MEASUREMENT_THREAD_UP(thread) \
++ (BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT)
++#define CPR3_LAST_MEASUREMENT_AGGR_DN BIT(8)
++#define CPR3_LAST_MEASUREMENT_AGGR_MID BIT(9)
++#define CPR3_LAST_MEASUREMENT_AGGR_UP BIT(10)
++#define CPR3_LAST_MEASUREMENT_VALID BIT(11)
++#define CPR3_LAST_MEASUREMENT_SAW_ERROR BIT(12)
++#define CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK GENMASK(23, 16)
++#define CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT 16
++
++/* CPR4 controller specific registers and bit definitions */
++#define CPR4_REG_CPR_TIMER_CLAMP 0x10
++#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN BIT(27)
++
++#define CPR4_REG_MISC 0x700
++#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK GENMASK(23, 20)
++#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT 20
++#define CPR4_MISC_TEMP_SENSOR_ID_START_MASK GENMASK(27, 24)
++#define CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT 24
++#define CPR4_MISC_TEMP_SENSOR_ID_END_MASK GENMASK(31, 28)
++#define CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT 28
++
++#define CPR4_REG_SAW_ERROR_STEP_LIMIT 0x7A4
++#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK GENMASK(4, 0)
++#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT 0
++#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK GENMASK(9, 5)
++#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT 5
++
++#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS 0x7A8
++#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK GENMASK(28, 18)
++#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT 18
++
++#define CPR4_REG_MARGIN_TEMP_CORE(core) (0x7AC + 0x4 * (core))
++#define CPR4_MARGIN_TEMP_CORE_ADJ_MASK GENMASK(7, 0)
++#define CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT 8
++
++#define CPR4_REG_MARGIN_TEMP_POINT0N1 0x7F0
++#define CPR4_MARGIN_TEMP_POINT0_MASK GENMASK(11, 0)
++#define CPR4_MARGIN_TEMP_POINT0_SHIFT 0
++#define CPR4_MARGIN_TEMP_POINT1_MASK GENMASK(23, 12)
++#define CPR4_MARGIN_TEMP_POINT1_SHIFT 12
++#define CPR4_REG_MARGIN_TEMP_POINT2 0x7F4
++#define CPR4_MARGIN_TEMP_POINT2_MASK GENMASK(11, 0)
++#define CPR4_MARGIN_TEMP_POINT2_SHIFT 0
++
++#define CPR4_REG_MARGIN_ADJ_CTL 0x7F8
++#define CPR4_MARGIN_ADJ_CTL_BOOST_EN BIT(0)
++#define CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN BIT(1)
++#define CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN BIT(2)
++#define CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN BIT(3)
++#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK BIT(4)
++#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE BIT(4)
++#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE 0
++#define CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN BIT(7)
++#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN BIT(8)
++#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK GENMASK(16, 12)
++#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT 12
++#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK GENMASK(21, 19)
++#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT 19
++#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK GENMASK(25, 22)
++#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT 22
++#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK GENMASK(31, 26)
++#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT 26
++
++#define CPR4_REG_CPR_MASK_THREAD(thread) (0x80C + 0x440 * (thread))
++#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD BIT(31)
++#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK GENMASK(15, 0)
++
++/*
++ * The amount of time to wait for the CPR controller to become idle when
++ * performing an aging measurement.
++ */
++#define CPR3_AGING_MEASUREMENT_TIMEOUT_NS 5000000
++
++/*
++ * The number of individual aging measurements to perform which are then
++ * averaged together in order to determine the final aging adjustment value.
++ */
++#define CPR3_AGING_MEASUREMENT_ITERATIONS 16
++
++/*
++ * Aging measurements for the aged and unaged ring oscillators take place a few
++ * microseconds apart. If the vdd-supply voltage fluctuates between the two
++ * measurements, then the difference between them will be incorrect. The
++ * difference could end up too high or too low. This constant defines the
++ * number of lowest and highest measurements to ignore when averaging.
++ */
++#define CPR3_AGING_MEASUREMENT_FILTER 3
++
++/*
++ * The number of times to attempt the full aging measurement sequence before
++ * declaring a measurement failure.
++ */
++#define CPR3_AGING_RETRY_COUNT 5
++
++/*
++ * The maximum time to wait in microseconds for a CPR register write to
++ * complete.
++ */
++#define CPR3_REGISTER_WRITE_DELAY_US 200
++
++static DEFINE_MUTEX(cpr3_controller_list_mutex);
++static LIST_HEAD(cpr3_controller_list);
++static struct dentry *cpr3_debugfs_base;
++
++/**
++ * cpr3_read() - read four bytes from the memory address specified
++ * @ctrl: Pointer to the CPR3 controller
++ * @offset: Offset in bytes from the CPR3 controller's base address
++ *
++ * Return: memory address value
++ */
++static inline u32 cpr3_read(struct cpr3_controller *ctrl, u32 offset)
++{
++ if (!ctrl->cpr_enabled) {
++ cpr3_err(ctrl, "CPR register reads are not possible when CPR clocks are disabled\n");
++ return 0;
++ }
++
++ return readl_relaxed(ctrl->cpr_ctrl_base + offset);
++}
++
++/**
++ * cpr3_write() - write four bytes to the memory address specified
++ * @ctrl: Pointer to the CPR3 controller
++ * @offset: Offset in bytes from the CPR3 controller's base address
++ * @value: Value to write to the memory address
++ *
++ * Return: none
++ */
++static inline void cpr3_write(struct cpr3_controller *ctrl, u32 offset,
++ u32 value)
++{
++ if (!ctrl->cpr_enabled) {
++ cpr3_err(ctrl, "CPR register writes are not possible when CPR clocks are disabled\n");
++ return;
++ }
++
++ writel_relaxed(value, ctrl->cpr_ctrl_base + offset);
++}
++
++/**
++ * cpr3_masked_write() - perform a read-modify-write sequence so that only
++ * masked bits are modified
++ * @ctrl: Pointer to the CPR3 controller
++ * @offset: Offset in bytes from the CPR3 controller's base address
++ * @mask: Mask identifying the bits that should be modified
++ * @value: Value to write to the memory address
++ *
++ * Return: none
++ */
++static inline void cpr3_masked_write(struct cpr3_controller *ctrl, u32 offset,
++ u32 mask, u32 value)
++{
++ u32 reg_val, orig_val;
++
++ if (!ctrl->cpr_enabled) {
++ cpr3_err(ctrl, "CPR register writes are not possible when CPR clocks are disabled\n");
++ return;
++ }
++
++ reg_val = orig_val = readl_relaxed(ctrl->cpr_ctrl_base + offset);
++ reg_val &= ~mask;
++ reg_val |= value & mask;
++
++ if (reg_val != orig_val)
++ writel_relaxed(reg_val, ctrl->cpr_ctrl_base + offset);
++}
++
++/**
++ * cpr3_ctrl_loop_enable() - enable the CPR sensing loop for a given controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: none
++ */
++static inline void cpr3_ctrl_loop_enable(struct cpr3_controller *ctrl)
++{
++ if (ctrl->cpr_enabled && !(ctrl->aggr_corner.sdelta
++ && ctrl->aggr_corner.sdelta->allow_boost))
++ cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
++ CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_ENABLE);
++}
++
++/**
++ * cpr3_ctrl_loop_disable() - disable the CPR sensing loop for a given
++ * controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: none
++ */
++static inline void cpr3_ctrl_loop_disable(struct cpr3_controller *ctrl)
++{
++ if (ctrl->cpr_enabled)
++ cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
++ CPR3_CPR_CTL_LOOP_EN_MASK, CPR3_CPR_CTL_LOOP_DISABLE);
++}
++
++/**
++ * cpr3_clock_enable() - prepare and enable all clocks used by this CPR3
++ * controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_clock_enable(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ rc = clk_prepare_enable(ctrl->bus_clk);
++ if (rc) {
++ cpr3_err(ctrl, "failed to enable bus clock, rc=%d\n", rc);
++ return rc;
++ }
++
++ rc = clk_prepare_enable(ctrl->iface_clk);
++ if (rc) {
++ cpr3_err(ctrl, "failed to enable interface clock, rc=%d\n", rc);
++ clk_disable_unprepare(ctrl->bus_clk);
++ return rc;
++ }
++
++ rc = clk_prepare_enable(ctrl->core_clk);
++ if (rc) {
++ cpr3_err(ctrl, "failed to enable core clock, rc=%d\n", rc);
++ clk_disable_unprepare(ctrl->iface_clk);
++ clk_disable_unprepare(ctrl->bus_clk);
++ return rc;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_clock_disable() - disable and unprepare all clocks used by this CPR3
++ * controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: none
++ */
++static void cpr3_clock_disable(struct cpr3_controller *ctrl)
++{
++ clk_disable_unprepare(ctrl->core_clk);
++ clk_disable_unprepare(ctrl->iface_clk);
++ clk_disable_unprepare(ctrl->bus_clk);
++}
++
++/**
++ * cpr3_ctrl_clear_cpr4_config() - clear the CPR4 register configuration
++ * programmed for current aggregated corner of a given controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static inline int cpr3_ctrl_clear_cpr4_config(struct cpr3_controller *ctrl)
++{
++ struct cpr4_sdelta *aggr_sdelta = ctrl->aggr_corner.sdelta;
++ bool cpr_enabled = ctrl->cpr_enabled;
++ int i, rc = 0;
++
++ if (!aggr_sdelta || !(aggr_sdelta->allow_core_count_adj
++ || aggr_sdelta->allow_temp_adj || aggr_sdelta->allow_boost))
++ /* cpr4 features are not enabled */
++ return 0;
++
++ /* Ensure that CPR clocks are enabled before writing to registers. */
++ if (!cpr_enabled) {
++ rc = cpr3_clock_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
++ return rc;
++ }
++ ctrl->cpr_enabled = true;
++ }
++
++ /*
++ * Clear feature enable configuration made for current
++ * aggregated corner.
++ */
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
++ | CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
++ | CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
++ | CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
++ | CPR4_MARGIN_ADJ_CTL_BOOST_EN
++ | CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK, 0);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MISC,
++ CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
++ 0 << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
++
++ for (i = 0; i <= aggr_sdelta->max_core_count; i++) {
++ /* Clear voltage margin adjustments programmed in TEMP_COREi */
++ cpr3_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE(i), 0);
++ }
++
++ /* Turn off CPR clocks if they were off before this function call. */
++ if (!cpr_enabled) {
++ cpr3_clock_disable(ctrl);
++ ctrl->cpr_enabled = false;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_closed_loop_enable() - enable logical CPR closed-loop operation
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_closed_loop_enable(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ if (!ctrl->cpr_allowed_hw || !ctrl->cpr_allowed_sw) {
++ cpr3_err(ctrl, "cannot enable closed-loop CPR operation because it is disallowed\n");
++ return -EPERM;
++ } else if (ctrl->cpr_enabled) {
++ /* Already enabled */
++ return 0;
++ } else if (ctrl->cpr_suspended) {
++ /*
++ * CPR must remain disabled as the system is entering suspend.
++ */
++ return 0;
++ }
++
++ rc = cpr3_clock_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "unable to enable CPR clocks, rc=%d\n", rc);
++ return rc;
++ }
++
++ ctrl->cpr_enabled = true;
++ cpr3_debug(ctrl, "CPR closed-loop operation enabled\n");
++
++ return 0;
++}
++
++/**
++ * cpr3_closed_loop_disable() - disable logical CPR closed-loop operation
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static inline int cpr3_closed_loop_disable(struct cpr3_controller *ctrl)
++{
++ if (!ctrl->cpr_enabled) {
++ /* Already disabled */
++ return 0;
++ }
++
++ cpr3_clock_disable(ctrl);
++ ctrl->cpr_enabled = false;
++ cpr3_debug(ctrl, "CPR closed-loop operation disabled\n");
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_get_gcnt() - returns the GCNT register value corresponding
++ * to the clock rate and sensor time of the CPR3 controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: GCNT value
++ */
++static u32 cpr3_regulator_get_gcnt(struct cpr3_controller *ctrl)
++{
++ u64 temp;
++ unsigned int remainder;
++ u32 gcnt;
++
++ temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->sensor_time;
++ remainder = do_div(temp, 1000000000);
++ if (remainder)
++ temp++;
++ /*
++ * GCNT == 0 corresponds to a single ref clock measurement interval so
++ * offset GCNT values by 1.
++ */
++ gcnt = temp - 1;
++
++ return gcnt;
++}
++
++/**
++ * cpr3_regulator_init_thread() - performs hardware initialization of CPR
++ * thread registers
++ * @thread: Pointer to the CPR3 thread
++ *
++ * CPR interface/bus clocks must be enabled before calling this function.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_init_thread(struct cpr3_thread *thread)
++{
++ u32 reg;
++
++ reg = (thread->consecutive_up << CPR3_THRESH_CONS_UP_SHIFT)
++ & CPR3_THRESH_CONS_UP_MASK;
++ reg |= (thread->consecutive_down << CPR3_THRESH_CONS_DOWN_SHIFT)
++ & CPR3_THRESH_CONS_DOWN_MASK;
++ reg |= (thread->up_threshold << CPR3_THRESH_UP_THRESH_SHIFT)
++ & CPR3_THRESH_UP_THRESH_MASK;
++ reg |= (thread->down_threshold << CPR3_THRESH_DOWN_THRESH_SHIFT)
++ & CPR3_THRESH_DOWN_THRESH_MASK;
++
++ cpr3_write(thread->ctrl, CPR3_REG_THRESH(thread->thread_id), reg);
++
++ /*
++ * Mask all RO's initially so that unused thread doesn't contribute
++ * to closed-loop voltage.
++ */
++ cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
++ CPR3_RO_MASK);
++
++ return 0;
++}
++
++/**
++ * cpr4_regulator_init_temp_points() - performs hardware initialization of CPR4
++ * registers to track tsen temperature data and also specify the
++ * temperature band range values to apply different voltage margins
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * CPR interface/bus clocks must be enabled before calling this function.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_regulator_init_temp_points(struct cpr3_controller *ctrl)
++{
++ if (!ctrl->allow_temp_adj)
++ return 0;
++
++ cpr3_masked_write(ctrl, CPR4_REG_MISC,
++ CPR4_MISC_TEMP_SENSOR_ID_START_MASK,
++ ctrl->temp_sensor_id_start
++ << CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MISC,
++ CPR4_MISC_TEMP_SENSOR_ID_END_MASK,
++ ctrl->temp_sensor_id_end
++ << CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT2,
++ CPR4_MARGIN_TEMP_POINT2_MASK,
++ (ctrl->temp_band_count == 4 ? ctrl->temp_points[2] : 0x7FF)
++ << CPR4_MARGIN_TEMP_POINT2_SHIFT);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
++ CPR4_MARGIN_TEMP_POINT1_MASK,
++ (ctrl->temp_band_count >= 3 ? ctrl->temp_points[1] : 0x7FF)
++ << CPR4_MARGIN_TEMP_POINT1_SHIFT);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_POINT0N1,
++ CPR4_MARGIN_TEMP_POINT0_MASK,
++ (ctrl->temp_band_count >= 2 ? ctrl->temp_points[0] : 0x7FF)
++ << CPR4_MARGIN_TEMP_POINT0_SHIFT);
++ return 0;
++}
++
++/**
++ * cpr3_regulator_init_cpr4() - performs hardware initialization at the
++ * controller and thread level required for CPR4 operation.
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * CPR interface/bus clocks must be enabled before calling this function.
++ * This function allocates sdelta structures and sdelta tables for aggregated
++ * corners of the controller and its threads.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
++{
++ struct cpr3_thread *thread;
++ struct cpr3_regulator *vreg;
++ struct cpr4_sdelta *sdelta;
++ int i, j, ctrl_max_core_count, thread_max_core_count, rc = 0;
++ bool ctrl_valid_sdelta, thread_valid_sdelta;
++ u32 pmic_step_size = 1;
++ int thread_id = 0;
++ u64 temp;
++
++ if (ctrl->supports_hw_closed_loop) {
++ if (ctrl->saw_use_unit_mV)
++ pmic_step_size = ctrl->step_volt / 1000;
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK,
++ (pmic_step_size
++ << CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT));
++
++ cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
++ CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK,
++ (ctrl->down_error_step_limit
++ << CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT));
++
++ cpr3_masked_write(ctrl, CPR4_REG_SAW_ERROR_STEP_LIMIT,
++ CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK,
++ (ctrl->up_error_step_limit
++ << CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));
++
++ /*
++ * Enable thread aggregation regardless of which threads are
++ * enabled or disabled.
++ */
++ cpr3_masked_write(ctrl, CPR4_REG_CPR_TIMER_CLAMP,
++ CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
++ CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN);
++
++ switch (ctrl->thread_count) {
++ case 0:
++ /* Disable both threads */
++ cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(0),
++ CPR4_CPR_MASK_THREAD_DISABLE_THREAD
++ | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
++ CPR4_CPR_MASK_THREAD_DISABLE_THREAD
++ | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
++
++ cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(1),
++ CPR4_CPR_MASK_THREAD_DISABLE_THREAD
++ | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
++ CPR4_CPR_MASK_THREAD_DISABLE_THREAD
++ | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
++ break;
++ case 1:
++ /* Disable unused thread */
++ thread_id = ctrl->thread[0].thread_id ? 0 : 1;
++ cpr3_masked_write(ctrl,
++ CPR4_REG_CPR_MASK_THREAD(thread_id),
++ CPR4_CPR_MASK_THREAD_DISABLE_THREAD
++ | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
++ CPR4_CPR_MASK_THREAD_DISABLE_THREAD
++ | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
++ break;
++ }
++ }
++
++ if (!ctrl->allow_core_count_adj && !ctrl->allow_temp_adj
++ && !ctrl->allow_boost) {
++ /*
++ * Skip below configuration as none of the features
++ * are enabled.
++ */
++ return rc;
++ }
++
++ if (ctrl->supports_hw_closed_loop)
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN,
++ CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK,
++ ctrl->step_quot_fixed
++ << CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT);
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN,
++ (ctrl->use_dynamic_step_quot
++ ? CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN : 0));
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK,
++ ctrl->initial_temp_band
++ << CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT);
++
++ rc = cpr4_regulator_init_temp_points(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "initialize temp points failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ if (ctrl->voltage_settling_time) {
++ /*
++ * Configure the settling timer used to account for
++ * one VDD supply step.
++ */
++ temp = (u64)ctrl->cpr_clock_rate
++ * (u64)ctrl->voltage_settling_time;
++ do_div(temp, 1000000000);
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_TEMP_CORE_TIMERS,
++ CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK,
++ temp
++ << CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT);
++ }
++
++ /*
++ * Allocate memory for cpr4_sdelta structure and sdelta table for
++ * controller aggregated corner by finding the maximum core count
++ * used by any cpr3 regulators.
++ */
++ ctrl_max_core_count = 1;
++ ctrl_valid_sdelta = false;
++ for (i = 0; i < ctrl->thread_count; i++) {
++ thread = &ctrl->thread[i];
++
++ /*
++ * Allocate memory for cpr4_sdelta structure and sdelta table
++ * for thread aggregated corner by finding the maximum core
++ * count used by any cpr3 regulators of the thread.
++ */
++ thread_max_core_count = 1;
++ thread_valid_sdelta = false;
++ for (j = 0; j < thread->vreg_count; j++) {
++ vreg = &thread->vreg[j];
++ thread_max_core_count = max(thread_max_core_count,
++ vreg->max_core_count);
++ thread_valid_sdelta |= (vreg->allow_core_count_adj
++ | vreg->allow_temp_adj
++ | vreg->allow_boost);
++ }
++ if (thread_valid_sdelta) {
++ sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta),
++ GFP_KERNEL);
++ if (!sdelta)
++ return -ENOMEM;
++
++ sdelta->table = devm_kcalloc(ctrl->dev,
++ thread_max_core_count
++ * ctrl->temp_band_count,
++ sizeof(*sdelta->table),
++ GFP_KERNEL);
++ if (!sdelta->table)
++ return -ENOMEM;
++
++ sdelta->boost_table = devm_kcalloc(ctrl->dev,
++ ctrl->temp_band_count,
++ sizeof(*sdelta->boost_table),
++ GFP_KERNEL);
++ if (!sdelta->boost_table)
++ return -ENOMEM;
++
++ thread->aggr_corner.sdelta = sdelta;
++ }
++
++ ctrl_valid_sdelta |= thread_valid_sdelta;
++ ctrl_max_core_count = max(ctrl_max_core_count,
++ thread_max_core_count);
++ }
++
++ if (ctrl_valid_sdelta) {
++ sdelta = devm_kzalloc(ctrl->dev, sizeof(*sdelta), GFP_KERNEL);
++ if (!sdelta)
++ return -ENOMEM;
++
++ sdelta->table = devm_kcalloc(ctrl->dev, ctrl_max_core_count
++ * ctrl->temp_band_count,
++ sizeof(*sdelta->table), GFP_KERNEL);
++ if (!sdelta->table)
++ return -ENOMEM;
++
++ sdelta->boost_table = devm_kcalloc(ctrl->dev,
++ ctrl->temp_band_count,
++ sizeof(*sdelta->boost_table),
++ GFP_KERNEL);
++ if (!sdelta->boost_table)
++ return -ENOMEM;
++
++ ctrl->aggr_corner.sdelta = sdelta;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_write_temp_core_margin() - programs hardware SDELTA registers with
++ * the voltage margin adjustments that need to be applied for
++ * different online core-count and temperature bands.
++ * @ctrl: Pointer to the CPR3 controller
++ * @addr: SDELTA register address
++ * @temp_core_adj: Array of voltage margin values for different temperature
++ * bands.
++ *
++ * CPR interface/bus clocks must be enabled before calling this function.
++ *
++ * Return: none
++ */
++static void cpr3_write_temp_core_margin(struct cpr3_controller *ctrl,
++ int addr, int *temp_core_adj)
++{
++ int i, margin_steps;
++ u32 reg = 0;
++
++ for (i = 0; i < ctrl->temp_band_count; i++) {
++ margin_steps = max(min(temp_core_adj[i], 127), -128);
++ reg |= (margin_steps & CPR4_MARGIN_TEMP_CORE_ADJ_MASK) <<
++ (i * CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT);
++ }
++
++ cpr3_write(ctrl, addr, reg);
++ cpr3_debug(ctrl, "sdelta offset=0x%08x, val=0x%08x\n", addr, reg);
++}
++
++/**
++ * cpr3_controller_program_sdelta() - programs hardware SDELTA registers with
++ * the voltage margin adjustments that need to be applied at
++ * different online core-count and temperature bands. Also,
++ * programs hardware register configuration for per-online-core
++ * and per-temperature based adjustments.
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * CPR interface/bus clocks must be enabled before calling this function.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_controller_program_sdelta(struct cpr3_controller *ctrl)
++{
++ struct cpr3_corner *corner = &ctrl->aggr_corner;
++ struct cpr4_sdelta *sdelta = corner->sdelta;
++ int i, index, max_core_count, rc = 0;
++ bool cpr_enabled = ctrl->cpr_enabled;
++
++ if (!sdelta)
++ /* cpr4_sdelta not defined for current aggregated corner */
++ return 0;
++
++ if (ctrl->supports_hw_closed_loop && ctrl->cpr_enabled) {
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
++ (ctrl->use_hw_closed_loop && !sdelta->allow_boost)
++ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0);
++ }
++
++ if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj
++ && !sdelta->allow_boost) {
++ /*
++ * Per-online-core, per-temperature and voltage boost
++ * adjustments are disabled for this aggregation corner.
++ */
++ return 0;
++ }
++
++ /* Ensure that CPR clocks are enabled before writing to registers. */
++ if (!cpr_enabled) {
++ rc = cpr3_clock_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
++ return rc;
++ }
++ ctrl->cpr_enabled = true;
++ }
++
++ max_core_count = sdelta->max_core_count;
++
++ if (sdelta->allow_core_count_adj || sdelta->allow_temp_adj) {
++ if (sdelta->allow_core_count_adj) {
++ /* Program TEMP_CORE0 to same margins as TEMP_CORE1 */
++ cpr3_write_temp_core_margin(ctrl,
++ CPR4_REG_MARGIN_TEMP_CORE(0),
++ &sdelta->table[0]);
++ }
++
++ for (i = 0; i < max_core_count; i++) {
++ index = i * sdelta->temp_band_count;
++ /*
++ * Program TEMP_COREi with voltage margin adjustments
++ * that need to be applied when the number of cores
++ * becomes i.
++ */
++ cpr3_write_temp_core_margin(ctrl,
++ CPR4_REG_MARGIN_TEMP_CORE(
++ sdelta->allow_core_count_adj
++ ? i + 1 : max_core_count),
++ &sdelta->table[index]);
++ }
++ }
++
++ if (sdelta->allow_boost) {
++ /* Program only boost_num_cores row of SDELTA */
++ cpr3_write_temp_core_margin(ctrl,
++ CPR4_REG_MARGIN_TEMP_CORE(sdelta->boost_num_cores),
++ &sdelta->boost_table[0]);
++ }
++
++ if (!sdelta->allow_core_count_adj && !sdelta->allow_boost) {
++ cpr3_masked_write(ctrl, CPR4_REG_MISC,
++ CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
++ max_core_count
++ << CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT);
++ }
++
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK
++ | CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN
++ | CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN
++ | CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN
++ | CPR4_MARGIN_ADJ_CTL_BOOST_EN,
++ max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
++ | ((sdelta->allow_core_count_adj || sdelta->allow_boost)
++ ? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
++ | ((sdelta->allow_temp_adj && ctrl->supports_hw_closed_loop)
++ ? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
++ | (((ctrl->use_hw_closed_loop && !sdelta->allow_boost)
++ || !ctrl->supports_hw_closed_loop)
++ ? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
++ | (sdelta->allow_boost
++ ? CPR4_MARGIN_ADJ_CTL_BOOST_EN : 0));
++
++ /*
++ * Ensure that all previous CPR register writes have completed before
++ * continuing.
++ */
++ mb();
++
++ /* Turn off CPR clocks if they were off before this function call. */
++ if (!cpr_enabled) {
++ cpr3_clock_disable(ctrl);
++ ctrl->cpr_enabled = false;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_init_ctrl() - performs hardware initialization of CPR
++ * controller registers
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
++{
++ int i, j, k, m, rc;
++ u32 ro_used = 0;
++ u32 gcnt, cont_dly, up_down_dly, val;
++ u64 temp;
++ char *mode;
++
++ if (ctrl->core_clk) {
++ rc = clk_set_rate(ctrl->core_clk, ctrl->cpr_clock_rate);
++ if (rc) {
++ cpr3_err(ctrl, "clk_set_rate(core_clk, %u) failed, rc=%d\n",
++ ctrl->cpr_clock_rate, rc);
++ return rc;
++ }
++ }
++
++ rc = cpr3_clock_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
++ return rc;
++ }
++ ctrl->cpr_enabled = true;
++
++ /* Find all RO's used by any corner of any regulator. */
++ for (i = 0; i < ctrl->thread_count; i++)
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++)
++ for (k = 0; k < ctrl->thread[i].vreg[j].corner_count;
++ k++)
++ for (m = 0; m < CPR3_RO_COUNT; m++)
++ if (ctrl->thread[i].vreg[j].corner[k].
++ target_quot[m])
++ ro_used |= BIT(m);
++
++ /* Configure the GCNT of the RO's that will be used */
++ gcnt = cpr3_regulator_get_gcnt(ctrl);
++ for (i = 0; i < CPR3_RO_COUNT; i++)
++ if (ro_used & BIT(i))
++ cpr3_write(ctrl, CPR3_REG_GCNT(i), gcnt);
++
++ /* Configure the loop delay time */
++ temp = (u64)ctrl->cpr_clock_rate * (u64)ctrl->loop_time;
++ do_div(temp, 1000000000);
++ cont_dly = temp;
++ if (ctrl->supports_hw_closed_loop
++ && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, cont_dly);
++ else
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT, cont_dly);
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ temp = (u64)ctrl->cpr_clock_rate *
++ (u64)ctrl->up_down_delay_time;
++ do_div(temp, 1000000000);
++ up_down_dly = temp;
++ if (ctrl->supports_hw_closed_loop)
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT,
++ up_down_dly);
++ cpr3_debug(ctrl, "up_down_dly=%u, up_down_delay_time=%u ns\n",
++ up_down_dly, ctrl->up_down_delay_time);
++ }
++
++ cpr3_debug(ctrl, "cpr_clock_rate=%u HZ, sensor_time=%u ns, loop_time=%u ns, gcnt=%u, cont_dly=%u\n",
++ ctrl->cpr_clock_rate, ctrl->sensor_time, ctrl->loop_time,
++ gcnt, cont_dly);
++
++ /* Configure CPR sensor operation */
++ val = (ctrl->idle_clocks << CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT)
++ & CPR3_CPR_CTL_IDLE_CLOCKS_MASK;
++ val |= (ctrl->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT)
++ & CPR3_CPR_CTL_COUNT_MODE_MASK;
++ val |= (ctrl->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT)
++ & CPR3_CPR_CTL_COUNT_REPEAT_MASK;
++ cpr3_write(ctrl, CPR3_REG_CPR_CTL, val);
++
++ cpr3_debug(ctrl, "idle_clocks=%u, count_mode=%u, count_repeat=%u; CPR_CTL=0x%08X\n",
++ ctrl->idle_clocks, ctrl->count_mode, ctrl->count_repeat, val);
++
++ /* Configure CPR default step quotients */
++ val = (ctrl->step_quot_init_min << CPR3_CPR_STEP_QUOT_MIN_SHIFT)
++ & CPR3_CPR_STEP_QUOT_MIN_MASK;
++ val |= (ctrl->step_quot_init_max << CPR3_CPR_STEP_QUOT_MAX_SHIFT)
++ & CPR3_CPR_STEP_QUOT_MAX_MASK;
++ cpr3_write(ctrl, CPR3_REG_CPR_STEP_QUOT, val);
++
++ cpr3_debug(ctrl, "step_quot_min=%u, step_quot_max=%u; STEP_QUOT=0x%08X\n",
++ ctrl->step_quot_init_min, ctrl->step_quot_init_max, val);
++
++ /* Configure the CPR sensor ownership */
++ for (i = 0; i < ctrl->sensor_count; i++)
++ cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(i),
++ ctrl->sensor_owner[i]);
++
++ /* Configure per-thread registers */
++ for (i = 0; i < ctrl->thread_count; i++) {
++ rc = cpr3_regulator_init_thread(&ctrl->thread[i]);
++ if (rc) {
++ cpr3_err(ctrl, "CPR thread register initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ if (ctrl->supports_hw_closed_loop) {
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
++ ctrl->use_hw_closed_loop
++ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
++ : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
++ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
++ ctrl->use_hw_closed_loop
++ ? CPR3_HW_CLOSED_LOOP_ENABLE
++ : CPR3_HW_CLOSED_LOOP_DISABLE);
++
++ cpr3_debug(ctrl, "PD_THROTTLE=0x%08X\n",
++ ctrl->proc_clock_throttle);
++ }
++
++ if ((ctrl->use_hw_closed_loop ||
++ ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) &&
++ ctrl->vdd_limit_regulator) {
++ rc = regulator_enable(ctrl->vdd_limit_regulator);
++ if (rc) {
++ cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++ }
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_regulator_init_cpr4(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "CPR4-specific controller initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ /* Ensure that all register writes complete before disabling clocks. */
++ wmb();
++
++ cpr3_clock_disable(ctrl);
++ ctrl->cpr_enabled = false;
++
++ if (!ctrl->cpr_allowed_sw || !ctrl->cpr_allowed_hw)
++ mode = "open-loop";
++ else if (ctrl->supports_hw_closed_loop)
++ mode = ctrl->use_hw_closed_loop
++ ? "HW closed-loop" : "SW closed-loop";
++ else
++ mode = "closed-loop";
++
++ cpr3_info(ctrl, "Default CPR mode = %s", mode);
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_set_target_quot() - configure the target quotient for each
++ * RO of the CPR3 thread and set the RO mask
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: none
++ */
++static void cpr3_regulator_set_target_quot(struct cpr3_thread *thread)
++{
++ u32 new_quot, last_quot;
++ int i;
++
++ if (thread->aggr_corner.ro_mask == CPR3_RO_MASK
++ && thread->last_closed_loop_aggr_corner.ro_mask == CPR3_RO_MASK) {
++ /* Avoid writing target quotients since all RO's are masked. */
++ return;
++ } else if (thread->aggr_corner.ro_mask == CPR3_RO_MASK) {
++ cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
++ CPR3_RO_MASK);
++ thread->last_closed_loop_aggr_corner.ro_mask = CPR3_RO_MASK;
++ /*
++ * Only the RO_MASK register needs to be written since all
++ * RO's are masked.
++ */
++ return;
++ } else if (thread->aggr_corner.ro_mask
++ != thread->last_closed_loop_aggr_corner.ro_mask) {
++ cpr3_write(thread->ctrl, CPR3_REG_RO_MASK(thread->thread_id),
++ thread->aggr_corner.ro_mask);
++ }
++
++ for (i = 0; i < CPR3_RO_COUNT; i++) {
++ new_quot = thread->aggr_corner.target_quot[i];
++ last_quot = thread->last_closed_loop_aggr_corner.target_quot[i];
++ if (new_quot != last_quot)
++ cpr3_write(thread->ctrl,
++ CPR3_REG_TARGET_QUOT(thread->thread_id, i),
++ new_quot);
++ }
++
++ thread->last_closed_loop_aggr_corner = thread->aggr_corner;
++
++ return;
++}
++
++/**
++ * cpr3_update_vreg_closed_loop_volt() - update the last known settled
++ * closed loop voltage for a CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ * @vdd_volt: Last known settled voltage in microvolts for the
++ * VDD supply
++ * @reg_last_measurement: Value read from the LAST_MEASUREMENT register
++ *
++ * Return: none
++ */
++static void cpr3_update_vreg_closed_loop_volt(struct cpr3_regulator *vreg,
++ int vdd_volt, u32 reg_last_measurement)
++{
++ bool step_dn, step_up, aggr_step_up, aggr_step_dn, aggr_step_mid;
++ bool valid, pd_valid, saw_error;
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ struct cpr3_corner *corner;
++ u32 id;
++
++ if (vreg->last_closed_loop_corner == CPR3_REGULATOR_CORNER_INVALID)
++ return;
++ else
++ corner = &vreg->corner[vreg->last_closed_loop_corner];
++
++ if (vreg->thread->last_closed_loop_aggr_corner.ro_mask
++ == CPR3_RO_MASK || !vreg->aggregated) {
++ return;
++ } else if (!ctrl->cpr_enabled || !ctrl->last_corner_was_closed_loop) {
++ return;
++ } else if (ctrl->thread_count == 1
++ && vdd_volt >= corner->floor_volt
++ && vdd_volt <= corner->ceiling_volt) {
++ corner->last_volt = vdd_volt;
++ cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d\n",
++ vreg->last_closed_loop_corner, corner->last_volt,
++ vreg->last_closed_loop_corner,
++ corner->ceiling_volt,
++ vreg->last_closed_loop_corner,
++ corner->floor_volt);
++ return;
++ } else if (!ctrl->supports_hw_closed_loop) {
++ return;
++ } else if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPR3) {
++ corner->last_volt = vdd_volt;
++ cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d\n",
++ vreg->last_closed_loop_corner, corner->last_volt,
++ vreg->last_closed_loop_corner,
++ corner->ceiling_volt,
++ vreg->last_closed_loop_corner,
++ corner->floor_volt);
++ return;
++ }
++
++ /* CPR clocks are on and HW closed loop is supported */
++ valid = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_VALID);
++ if (!valid) {
++ cpr3_debug(vreg, "CPR_LAST_VALID_MEASUREMENT=0x%X valid bit not set\n",
++ reg_last_measurement);
++ return;
++ }
++
++ id = vreg->thread->thread_id;
++
++ step_dn
++ = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_THREAD_DN(id));
++ step_up
++ = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_THREAD_UP(id));
++ aggr_step_dn = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_DN);
++ aggr_step_mid
++ = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_MID);
++ aggr_step_up = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_AGGR_UP);
++ saw_error = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_SAW_ERROR);
++ pd_valid
++ = !((((reg_last_measurement & CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK)
++ >> CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT)
++ & vreg->pd_bypass_mask) == vreg->pd_bypass_mask);
++
++ if (!pd_valid) {
++ cpr3_debug(vreg, "CPR_LAST_VALID_MEASUREMENT=0x%X, all power domains bypassed\n",
++ reg_last_measurement);
++ return;
++ } else if (step_dn && step_up) {
++ cpr3_err(vreg, "both up and down status bits set, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
++ reg_last_measurement);
++ return;
++ } else if (aggr_step_dn && step_dn && vdd_volt < corner->last_volt
++ && vdd_volt >= corner->floor_volt) {
++ corner->last_volt = vdd_volt;
++ } else if (aggr_step_up && step_up && vdd_volt > corner->last_volt
++ && vdd_volt <= corner->ceiling_volt) {
++ corner->last_volt = vdd_volt;
++ } else if (aggr_step_mid
++ && vdd_volt >= corner->floor_volt
++ && vdd_volt <= corner->ceiling_volt) {
++ corner->last_volt = vdd_volt;
++ } else if (saw_error && (vdd_volt == corner->ceiling_volt
++ || vdd_volt == corner->floor_volt)) {
++ corner->last_volt = vdd_volt;
++ } else {
++ cpr3_debug(vreg, "last_volt not updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d, vdd_volt=%d, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
++ vreg->last_closed_loop_corner, corner->last_volt,
++ vreg->last_closed_loop_corner,
++ corner->ceiling_volt,
++ vreg->last_closed_loop_corner, corner->floor_volt,
++ vdd_volt, reg_last_measurement);
++ return;
++ }
++
++ cpr3_debug(vreg, "last_volt updated: last_volt[%d]=%d, ceiling_volt[%d]=%d, floor_volt[%d]=%d, CPR_LAST_VALID_MEASUREMENT=0x%X\n",
++ vreg->last_closed_loop_corner, corner->last_volt,
++ vreg->last_closed_loop_corner, corner->ceiling_volt,
++ vreg->last_closed_loop_corner, corner->floor_volt,
++ reg_last_measurement);
++}
++
++/**
++ * cpr3_regulator_mem_acc_bhs_used() - determines if mem-acc regulators powered
++ * through a BHS are associated with the CPR3 controller or any of
++ * the CPR3 regulators it controls.
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * This function determines if the CPR3 controller or any of its CPR3 regulators
++ * need to manage mem-acc regulators that are currently powered through a BHS
++ * and whose corner selection is based upon a particular voltage threshold.
++ *
++ * Return: true or false
++ */
++static bool cpr3_regulator_mem_acc_bhs_used(struct cpr3_controller *ctrl)
++{
++ struct cpr3_regulator *vreg;
++ int i, j;
++
++ if (!ctrl->mem_acc_threshold_volt)
++ return false;
++
++ if (ctrl->mem_acc_regulator)
++ return true;
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++
++ if (vreg->mem_acc_regulator)
++ return true;
++ }
++ }
++
++ return false;
++}
++
++/**
++ * cpr3_regulator_config_bhs_mem_acc() - configure the mem-acc regulator
++ * settings for hardware blocks currently powered through the BHS.
++ * @ctrl: Pointer to the CPR3 controller
++ * @new_volt: New voltage in microvolts that VDD supply needs to
++ * end up at
++ * @last_volt: Pointer to the last known voltage in microvolts for the
++ * VDD supply
++ * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
++ * corner aggregated from all CPR3 threads managed by the
++ * CPR3 controller
++ *
++ * This function programs the mem-acc regulator corners for CPR3 regulators
++ * whose LDO regulators are in bypassed state. The function also handles
++ * CPR3 controllers which utilize mem-acc regulators that operate independently
++ * from the LDO hardware and that must be programmed when the VDD supply
++ * crosses a particular voltage threshold.
++ *
++ * Return: 0 on success, errno on failure. If the VDD supply voltage is
++ * modified, last_volt is updated to reflect the new voltage setpoint.
++ */
++static int cpr3_regulator_config_bhs_mem_acc(struct cpr3_controller *ctrl,
++ int new_volt, int *last_volt,
++ struct cpr3_corner *aggr_corner)
++{
++ struct cpr3_regulator *vreg;
++ int i, j, rc, mem_acc_corn, safe_volt;
++ int mem_acc_volt = ctrl->mem_acc_threshold_volt;
++ int ref_volt;
++
++ if (!cpr3_regulator_mem_acc_bhs_used(ctrl))
++ return 0;
++
++ ref_volt = ctrl->use_hw_closed_loop ? aggr_corner->floor_volt :
++ new_volt;
++
++ if (((*last_volt < mem_acc_volt && mem_acc_volt <= ref_volt) ||
++ (*last_volt >= mem_acc_volt && mem_acc_volt > ref_volt))) {
++ if (ref_volt < *last_volt)
++ safe_volt = max(mem_acc_volt, aggr_corner->last_volt);
++ else
++ safe_volt = max(mem_acc_volt, *last_volt);
++
++ rc = regulator_set_voltage(ctrl->vdd_regulator, safe_volt,
++ new_volt < *last_volt ?
++ ctrl->aggr_corner.ceiling_volt :
++ new_volt);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
++ safe_volt, rc);
++ return rc;
++ }
++
++ *last_volt = safe_volt;
++
++ mem_acc_corn = ref_volt < mem_acc_volt ?
++ ctrl->mem_acc_corner_map[CPR3_MEM_ACC_LOW_CORNER] :
++ ctrl->mem_acc_corner_map[CPR3_MEM_ACC_HIGH_CORNER];
++
++ if (ctrl->mem_acc_regulator) {
++ rc = regulator_set_voltage(ctrl->mem_acc_regulator,
++ mem_acc_corn, mem_acc_corn);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
++ mem_acc_corn, rc);
++ return rc;
++ }
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++
++ if (!vreg->mem_acc_regulator)
++ continue;
++
++ rc = regulator_set_voltage(
++ vreg->mem_acc_regulator, mem_acc_corn,
++ mem_acc_corn);
++ if (rc) {
++ cpr3_err(vreg, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
++ mem_acc_corn, rc);
++ return rc;
++ }
++ }
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_switch_apm_mode() - switch the mode of the APM controller
++ * associated with a given CPR3 controller
++ * @ctrl: Pointer to the CPR3 controller
++ * @new_volt: New voltage in microvolts that VDD supply needs to
++ * end up at
++ * @last_volt: Pointer to the last known voltage in microvolts for the
++ * VDD supply
++ * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
++ * corner aggregated from all CPR3 threads managed by the
++ * CPR3 controller
++ *
++ * This function requests a switch of the APM mode while guaranteeing
++ * any LDO regulator hardware requirements are satisfied. The function must
++ * be called once it is known a new VDD supply setpoint crosses the APM
++ * voltage threshold.
++ *
++ * Return: 0 on success, errno on failure. If the VDD supply voltage is
++ * modified, last_volt is updated to reflect the new voltage setpoint.
++ */
++static int cpr3_regulator_switch_apm_mode(struct cpr3_controller *ctrl,
++ int new_volt, int *last_volt,
++ struct cpr3_corner *aggr_corner)
++{
++ struct regulator *vdd = ctrl->vdd_regulator;
++ int apm_volt = ctrl->apm_threshold_volt;
++ int orig_last_volt = *last_volt;
++ int rc;
++
++ rc = regulator_set_voltage(vdd, apm_volt, apm_volt);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
++ apm_volt, rc);
++ return rc;
++ }
++
++ *last_volt = apm_volt;
++
++ rc = msm_apm_set_supply(ctrl->apm, new_volt >= apm_volt
++ ? ctrl->apm_high_supply : ctrl->apm_low_supply);
++ if (rc) {
++ cpr3_err(ctrl, "APM switch failed, rc=%d\n", rc);
++ /* Roll back the voltage. */
++ regulator_set_voltage(vdd, orig_last_volt, INT_MAX);
++ *last_volt = orig_last_volt;
++ return rc;
++ }
++ return 0;
++}
++
++/**
++ * cpr3_regulator_config_voltage_crossings() - configure APM and mem-acc
++ * settings depending upon a new VDD supply setpoint
++ *
++ * @ctrl: Pointer to the CPR3 controller
++ * @new_volt: New voltage in microvolts that VDD supply needs to
++ * end up at
++ * @last_volt: Pointer to the last known voltage in microvolts for the
++ * VDD supply
++ * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
++ * corner aggregated from all CPR3 threads managed by the
++ * CPR3 controller
++ *
++ * This function handles the APM and mem-acc regulator reconfiguration if
++ * the new VDD supply voltage will result in crossing their respective voltage
++ * thresholds.
++ *
++ * Return: 0 on success, errno on failure. If the VDD supply voltage is
++ * modified, last_volt is updated to reflect the new voltage setpoint.
++ */
++static int cpr3_regulator_config_voltage_crossings(struct cpr3_controller *ctrl,
++ int new_volt, int *last_volt,
++ struct cpr3_corner *aggr_corner)
++{
++ bool apm_crossing = false, mem_acc_crossing = false;
++ bool mem_acc_bhs_used;
++ int apm_volt = ctrl->apm_threshold_volt;
++ int mem_acc_volt = ctrl->mem_acc_threshold_volt;
++ int ref_volt, rc;
++
++ if (ctrl->apm && apm_volt > 0
++ && ((*last_volt < apm_volt && apm_volt <= new_volt)
++ || (*last_volt >= apm_volt && apm_volt > new_volt)))
++ apm_crossing = true;
++
++ mem_acc_bhs_used = cpr3_regulator_mem_acc_bhs_used(ctrl);
++
++ ref_volt = ctrl->use_hw_closed_loop ? aggr_corner->floor_volt :
++ new_volt;
++
++ if (mem_acc_bhs_used &&
++ (((*last_volt < mem_acc_volt && mem_acc_volt <= ref_volt) ||
++ (*last_volt >= mem_acc_volt && mem_acc_volt > ref_volt))))
++ mem_acc_crossing = true;
++
++ if (apm_crossing && mem_acc_crossing) {
++ if ((new_volt < *last_volt && apm_volt >= mem_acc_volt) ||
++ (new_volt >= *last_volt && apm_volt < mem_acc_volt)) {
++ rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt,
++ last_volt,
++ aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to switch APM mode\n");
++ return rc;
++ }
++
++ rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
++ last_volt, aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
++ return rc;
++ }
++ } else {
++ rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
++ last_volt, aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
++ return rc;
++ }
++
++ rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt,
++ last_volt,
++ aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to switch APM mode\n");
++ return rc;
++ }
++ }
++ } else if (apm_crossing) {
++ rc = cpr3_regulator_switch_apm_mode(ctrl, new_volt, last_volt,
++ aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to switch APM mode\n");
++ return rc;
++ }
++ } else if (mem_acc_crossing) {
++ rc = cpr3_regulator_config_bhs_mem_acc(ctrl, new_volt,
++ last_volt, aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to configure BHS mem-acc settings\n");
++ return rc;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_config_mem_acc() - configure the corner of the mem-acc
++ * regulator associated with the CPR3 controller
++ * @ctrl: Pointer to the CPR3 controller
++ * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
++ * corner aggregated from all CPR3 threads managed by the
++ * CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_config_mem_acc(struct cpr3_controller *ctrl,
++ struct cpr3_corner *aggr_corner)
++{
++ int rc;
++
++ if (ctrl->mem_acc_regulator && aggr_corner->mem_acc_volt) {
++ rc = regulator_set_voltage(ctrl->mem_acc_regulator,
++ aggr_corner->mem_acc_volt,
++ aggr_corner->mem_acc_volt);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
++ aggr_corner->mem_acc_volt, rc);
++ return rc;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_scale_vdd_voltage() - scale the CPR controlled VDD supply
++ * voltage to the new level while satisfying any other hardware
++ * requirements
++ * @ctrl: Pointer to the CPR3 controller
++ * @new_volt: New voltage in microvolts that VDD supply needs to end
++ * up at
++ * @last_volt: Last known voltage in microvolts for the VDD supply
++ * @aggr_corner: Pointer to the CPR3 corner which corresponds to the max
++ * corner aggregated from all CPR3 threads managed by the
++ * CPR3 controller
++ *
++ * This function scales the CPR controlled VDD supply voltage from its
++ * current level to the new voltage that is specified. If the supply is
++ * configured to use the APM and the APM threshold is crossed as a result of
++ * the voltage scaling, then this function also stops at the APM threshold,
++ * switches the APM source, and finally sets the final new voltage.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_scale_vdd_voltage(struct cpr3_controller *ctrl,
++ int new_volt, int last_volt,
++ struct cpr3_corner *aggr_corner)
++{
++ struct regulator *vdd = ctrl->vdd_regulator;
++ int rc;
++
++ if (new_volt < last_volt) {
++ rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
++ if (rc)
++ return rc;
++ } else {
++ /* Increasing VDD voltage */
++ if (ctrl->system_regulator) {
++ rc = regulator_set_voltage(ctrl->system_regulator,
++ aggr_corner->system_volt, INT_MAX);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(system) == %d failed, rc=%d\n",
++ aggr_corner->system_volt, rc);
++ return rc;
++ }
++ }
++ }
++
++ rc = cpr3_regulator_config_voltage_crossings(ctrl, new_volt, &last_volt,
++ aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "unable to handle voltage threshold crossing configurations, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ /*
++ * Subtract a small amount from the min_uV parameter so that the
++ * set voltage request is not dropped by the framework due to being
++ * duplicate. This is needed in order to switch from hardware
++ * closed-loop to open-loop successfully.
++ */
++ rc = regulator_set_voltage(vdd, new_volt - (ctrl->cpr_enabled ? 0 : 1),
++ aggr_corner->ceiling_volt);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(vdd) == %d failed, rc=%d\n",
++ new_volt, rc);
++ return rc;
++ }
++
++ if (new_volt == last_volt && ctrl->supports_hw_closed_loop
++ && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ /*
++ * CPR4 features enforce voltage reprogramming when the last
++ * set voltage and new set voltage are same. This way, we can
++ * ensure that SAW PMIC STATUS register is updated with newly
++ * programmed voltage.
++ */
++ rc = regulator_sync_voltage(vdd);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_sync_voltage(vdd) == %d failed, rc=%d\n",
++ new_volt, rc);
++ return rc;
++ }
++ }
++
++ if (new_volt >= last_volt) {
++ rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
++ if (rc)
++ return rc;
++ } else {
++ /* Decreasing VDD voltage */
++ if (ctrl->system_regulator) {
++ rc = regulator_set_voltage(ctrl->system_regulator,
++ aggr_corner->system_volt, INT_MAX);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_set_voltage(system) == %d failed, rc=%d\n",
++ aggr_corner->system_volt, rc);
++ return rc;
++ }
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_get_dynamic_floor_volt() - returns the current dynamic floor
++ * voltage based upon static configurations and the state of all
++ * power domains during the last CPR measurement
++ * @ctrl: Pointer to the CPR3 controller
++ * @reg_last_measurement: Value read from the LAST_MEASUREMENT register
++ *
++ * When using HW closed-loop, the dynamic floor voltage is always returned
++ * regardless of the current state of the power domains.
++ *
++ * Return: dynamic floor voltage in microvolts or 0 if dynamic floor is not
++ * currently required
++ */
++static int cpr3_regulator_get_dynamic_floor_volt(struct cpr3_controller *ctrl,
++ u32 reg_last_measurement)
++{
++ int dynamic_floor_volt = 0;
++ struct cpr3_regulator *vreg;
++ bool valid, pd_valid;
++ u32 bypass_bits;
++ int i, j;
++
++ if (!ctrl->supports_hw_closed_loop)
++ return 0;
++
++ if (likely(!ctrl->use_hw_closed_loop)) {
++ valid = !!(reg_last_measurement & CPR3_LAST_MEASUREMENT_VALID);
++ bypass_bits
++ = (reg_last_measurement & CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK)
++ >> CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT;
++ } else {
++ /*
++ * Ensure that the dynamic floor voltage is always used for
++ * HW closed-loop since the conditions below cannot be evaluated
++ * after each CPR measurement.
++ */
++ valid = false;
++ bypass_bits = 0;
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++
++ if (!vreg->uses_dynamic_floor)
++ continue;
++
++ pd_valid = !((bypass_bits & vreg->pd_bypass_mask)
++ == vreg->pd_bypass_mask);
++
++ if (!valid || !pd_valid)
++ dynamic_floor_volt = max(dynamic_floor_volt,
++ vreg->corner[
++ vreg->dynamic_floor_corner].last_volt);
++ }
++ }
++
++ return dynamic_floor_volt;
++}
++
++/**
++ * cpr3_regulator_max_sdelta_diff() - returns the maximum voltage difference in
++ * microvolts that can result from different operating conditions
++ * for the specified sdelta struct
++ * @sdelta: Pointer to the sdelta structure
++ * @step_volt: Step size in microvolts between available set
++ * points of the VDD supply.
++ *
++ * Return: voltage difference between the highest and lowest adjustments if
++ * sdelta and sdelta->table are valid, else 0.
++ */
++static int cpr3_regulator_max_sdelta_diff(const struct cpr4_sdelta *sdelta,
++ int step_volt)
++{
++ int i, j, index, sdelta_min = INT_MAX, sdelta_max = INT_MIN;
++
++ if (!sdelta || !sdelta->table)
++ return 0;
++
++ for (i = 0; i < sdelta->max_core_count; i++) {
++ for (j = 0; j < sdelta->temp_band_count; j++) {
++ index = i * sdelta->temp_band_count + j;
++ sdelta_min = min(sdelta_min, sdelta->table[index]);
++ sdelta_max = max(sdelta_max, sdelta->table[index]);
++ }
++ }
++
++ return (sdelta_max - sdelta_min) * step_volt;
++}
++
++/**
++ * cpr3_regulator_aggregate_sdelta() - check open-loop voltages of current
++ * aggregated corner and current corner of a given regulator
++ * and adjust the sdelta strucuture data of aggregate corner.
++ * @aggr_corner: Pointer to accumulated aggregated corner which
++ * is both an input and an output
++ * @corner: Pointer to the corner to be aggregated with
++ * aggr_corner
++ * @step_volt: Step size in microvolts between available set
++ * points of the VDD supply.
++ *
++ * Return: none
++ */
++static void cpr3_regulator_aggregate_sdelta(
++ struct cpr3_corner *aggr_corner,
++ const struct cpr3_corner *corner, int step_volt)
++{
++ struct cpr4_sdelta *aggr_sdelta, *sdelta;
++ int aggr_core_count, core_count, temp_band_count;
++ u32 aggr_index, index;
++ int i, j, sdelta_size, cap_steps, adjust_sdelta;
++
++ aggr_sdelta = aggr_corner->sdelta;
++ sdelta = corner->sdelta;
++
++ if (aggr_corner->open_loop_volt < corner->open_loop_volt) {
++ /*
++ * Found the new dominant regulator as its open-loop requirement
++ * is higher than previous dominant regulator. Calculate cap
++ * voltage to limit the SDELTA values to make sure the runtime
++ * (Core-count/temp) adjustments do not violate other
++ * regulators' voltage requirements. Use cpr4_sdelta values of
++ * new dominant regulator.
++ */
++ aggr_sdelta->cap_volt = min(aggr_sdelta->cap_volt,
++ (corner->open_loop_volt -
++ aggr_corner->open_loop_volt));
++
++ /* Clear old data in the sdelta table */
++ sdelta_size = aggr_sdelta->max_core_count
++ * aggr_sdelta->temp_band_count;
++
++ if (aggr_sdelta->allow_core_count_adj
++ || aggr_sdelta->allow_temp_adj)
++ memset(aggr_sdelta->table, 0, sdelta_size
++ * sizeof(*aggr_sdelta->table));
++
++ if (sdelta->allow_temp_adj || sdelta->allow_core_count_adj) {
++ /* Copy new data in sdelta table */
++ sdelta_size = sdelta->max_core_count
++ * sdelta->temp_band_count;
++ if (sdelta->table)
++ memcpy(aggr_sdelta->table, sdelta->table,
++ sdelta_size * sizeof(*sdelta->table));
++ }
++
++ if (sdelta->allow_boost) {
++ memcpy(aggr_sdelta->boost_table, sdelta->boost_table,
++ sdelta->temp_band_count
++ * sizeof(*sdelta->boost_table));
++ aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
++ } else if (aggr_sdelta->allow_boost) {
++ for (i = 0; i < aggr_sdelta->temp_band_count; i++) {
++ adjust_sdelta = (corner->open_loop_volt
++ - aggr_corner->open_loop_volt)
++ / step_volt;
++ aggr_sdelta->boost_table[i] += adjust_sdelta;
++ aggr_sdelta->boost_table[i]
++ = min(aggr_sdelta->boost_table[i], 0);
++ }
++ }
++
++ aggr_corner->open_loop_volt = corner->open_loop_volt;
++ aggr_sdelta->allow_temp_adj = sdelta->allow_temp_adj;
++ aggr_sdelta->allow_core_count_adj
++ = sdelta->allow_core_count_adj;
++ aggr_sdelta->max_core_count = sdelta->max_core_count;
++ aggr_sdelta->temp_band_count = sdelta->temp_band_count;
++ } else if (aggr_corner->open_loop_volt > corner->open_loop_volt) {
++ /*
++ * Adjust the cap voltage if the open-loop requirement of new
++ * regulator is the next highest.
++ */
++ aggr_sdelta->cap_volt = min(aggr_sdelta->cap_volt,
++ (aggr_corner->open_loop_volt
++ - corner->open_loop_volt));
++
++ if (sdelta->allow_boost) {
++ for (i = 0; i < aggr_sdelta->temp_band_count; i++) {
++ adjust_sdelta = (aggr_corner->open_loop_volt
++ - corner->open_loop_volt)
++ / step_volt;
++ aggr_sdelta->boost_table[i] =
++ sdelta->boost_table[i] + adjust_sdelta;
++ aggr_sdelta->boost_table[i]
++ = min(aggr_sdelta->boost_table[i], 0);
++ }
++ aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
++ }
++ } else {
++ /*
++ * Found another dominant regulator with same open-loop
++ * requirement. Make cap voltage to '0'. Disable core-count
++ * adjustments as we couldn't support for both regulators.
++ * Keep enable temp based adjustments if enabled for both
++ * regulators and choose mininum margin adjustment values
++ * between them.
++ */
++ aggr_sdelta->cap_volt = 0;
++ aggr_sdelta->allow_core_count_adj = false;
++
++ if (aggr_sdelta->allow_temp_adj
++ && sdelta->allow_temp_adj) {
++ aggr_core_count = aggr_sdelta->max_core_count - 1;
++ core_count = sdelta->max_core_count - 1;
++ temp_band_count = sdelta->temp_band_count;
++ for (j = 0; j < temp_band_count; j++) {
++ aggr_index = aggr_core_count * temp_band_count
++ + j;
++ index = core_count * temp_band_count + j;
++ aggr_sdelta->table[aggr_index] =
++ min(aggr_sdelta->table[aggr_index],
++ sdelta->table[index]);
++ }
++ } else {
++ aggr_sdelta->allow_temp_adj = false;
++ }
++
++ if (sdelta->allow_boost) {
++ memcpy(aggr_sdelta->boost_table, sdelta->boost_table,
++ sdelta->temp_band_count
++ * sizeof(*sdelta->boost_table));
++ aggr_sdelta->boost_num_cores = sdelta->boost_num_cores;
++ }
++ }
++
++ /* Keep non-dominant clients boost enable state */
++ aggr_sdelta->allow_boost |= sdelta->allow_boost;
++ if (aggr_sdelta->allow_boost)
++ aggr_sdelta->allow_core_count_adj = false;
++
++ if (aggr_sdelta->cap_volt && !(aggr_sdelta->cap_volt == INT_MAX)) {
++ core_count = aggr_sdelta->max_core_count;
++ temp_band_count = aggr_sdelta->temp_band_count;
++ /*
++ * Convert cap voltage from uV to PMIC steps and use to limit
++ * sdelta margin adjustments.
++ */
++ cap_steps = aggr_sdelta->cap_volt / step_volt;
++ for (i = 0; i < core_count; i++)
++ for (j = 0; j < temp_band_count; j++) {
++ index = i * temp_band_count + j;
++ aggr_sdelta->table[index] =
++ min(aggr_sdelta->table[index],
++ cap_steps);
++ }
++ }
++}
++
++/**
++ * cpr3_regulator_aggregate_corners() - aggregate two corners together
++ * @aggr_corner: Pointer to accumulated aggregated corner which
++ * is both an input and an output
++ * @corner: Pointer to the corner to be aggregated with
++ * aggr_corner
++ * @aggr_quot: Flag indicating that target quotients should be
++ * aggregated as well.
++ * @step_volt: Step size in microvolts between available set
++ * points of the VDD supply.
++ *
++ * Return: none
++ */
++static void cpr3_regulator_aggregate_corners(struct cpr3_corner *aggr_corner,
++ const struct cpr3_corner *corner, bool aggr_quot,
++ int step_volt)
++{
++ int i;
++
++ aggr_corner->ceiling_volt
++ = max(aggr_corner->ceiling_volt, corner->ceiling_volt);
++ aggr_corner->floor_volt
++ = max(aggr_corner->floor_volt, corner->floor_volt);
++ aggr_corner->last_volt
++ = max(aggr_corner->last_volt, corner->last_volt);
++ aggr_corner->system_volt
++ = max(aggr_corner->system_volt, corner->system_volt);
++ aggr_corner->mem_acc_volt
++ = max(aggr_corner->mem_acc_volt, corner->mem_acc_volt);
++ aggr_corner->irq_en |= corner->irq_en;
++ aggr_corner->use_open_loop |= corner->use_open_loop;
++
++ if (aggr_quot) {
++ aggr_corner->ro_mask &= corner->ro_mask;
++
++ for (i = 0; i < CPR3_RO_COUNT; i++)
++ aggr_corner->target_quot[i]
++ = max(aggr_corner->target_quot[i],
++ corner->target_quot[i]);
++ }
++
++ if (aggr_corner->sdelta && corner->sdelta
++ && (aggr_corner->sdelta->table
++ || aggr_corner->sdelta->boost_table)) {
++ cpr3_regulator_aggregate_sdelta(aggr_corner, corner, step_volt);
++ } else {
++ aggr_corner->open_loop_volt
++ = max(aggr_corner->open_loop_volt,
++ corner->open_loop_volt);
++ }
++}
++
++/**
++ * cpr3_regulator_update_ctrl_state() - update the state of the CPR controller
++ * to reflect the corners used by all CPR3 regulators as well as
++ * the CPR operating mode
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * This function aggregates the CPR parameters for all CPR3 regulators
++ * associated with the VDD supply. Upon success, it sets the aggregated last
++ * known good voltage.
++ *
++ * The VDD supply voltage will not be physically configured unless this
++ * condition is met by at least one of the regulators of the controller:
++ * regulator->vreg_enabled == true &&
++ * regulator->current_corner != CPR3_REGULATOR_CORNER_INVALID
++ *
++ * CPR registers for the controller and each thread are updated as long as
++ * ctrl->cpr_enabled == true.
++ *
++ * Note, CPR3 controller lock must be held by the caller.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int _cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
++{
++ struct cpr3_corner aggr_corner = {};
++ struct cpr3_thread *thread;
++ struct cpr3_regulator *vreg;
++ struct cpr4_sdelta *sdelta;
++ bool valid = false;
++ bool thread_valid;
++ int i, j, rc, new_volt, vdd_volt, dynamic_floor_volt, last_corner_volt;
++ u32 reg_last_measurement = 0, sdelta_size;
++ int *sdelta_table, *boost_table;
++
++ last_corner_volt = 0;
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ vdd_volt = regulator_get_voltage(ctrl->vdd_regulator);
++ if (vdd_volt < 0) {
++ cpr3_err(ctrl, "regulator_get_voltage(vdd) failed, rc=%d\n",
++ vdd_volt);
++ return vdd_volt;
++ }
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ /*
++ * Save aggregated corner open-loop voltage which was programmed
++ * during last corner switch which is used when programming new
++ * aggregated corner open-loop voltage.
++ */
++ last_corner_volt = ctrl->aggr_corner.open_loop_volt;
++ }
++
++ if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop &&
++ ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
++ reg_last_measurement
++ = cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
++
++ aggr_corner.sdelta = ctrl->aggr_corner.sdelta;
++ if (aggr_corner.sdelta) {
++ sdelta = aggr_corner.sdelta;
++ sdelta_table = sdelta->table;
++ if (sdelta_table) {
++ sdelta_size = sdelta->max_core_count *
++ sdelta->temp_band_count;
++ memset(sdelta_table, 0, sdelta_size
++ * sizeof(*sdelta_table));
++ }
++
++ boost_table = sdelta->boost_table;
++ if (boost_table)
++ memset(boost_table, 0, sdelta->temp_band_count
++ * sizeof(*boost_table));
++
++ memset(sdelta, 0, sizeof(*sdelta));
++ sdelta->table = sdelta_table;
++ sdelta->cap_volt = INT_MAX;
++ sdelta->boost_table = boost_table;
++ }
++
++ /* Aggregate the requests of all threads */
++ for (i = 0; i < ctrl->thread_count; i++) {
++ thread = &ctrl->thread[i];
++ thread_valid = false;
++
++ sdelta = thread->aggr_corner.sdelta;
++ if (sdelta) {
++ sdelta_table = sdelta->table;
++ if (sdelta_table) {
++ sdelta_size = sdelta->max_core_count *
++ sdelta->temp_band_count;
++ memset(sdelta_table, 0, sdelta_size
++ * sizeof(*sdelta_table));
++ }
++
++ boost_table = sdelta->boost_table;
++ if (boost_table)
++ memset(boost_table, 0, sdelta->temp_band_count
++ * sizeof(*boost_table));
++
++ memset(sdelta, 0, sizeof(*sdelta));
++ sdelta->table = sdelta_table;
++ sdelta->cap_volt = INT_MAX;
++ sdelta->boost_table = boost_table;
++ }
++
++ memset(&thread->aggr_corner, 0, sizeof(thread->aggr_corner));
++ thread->aggr_corner.sdelta = sdelta;
++ thread->aggr_corner.ro_mask = CPR3_RO_MASK;
++
++ for (j = 0; j < thread->vreg_count; j++) {
++ vreg = &thread->vreg[j];
++
++ if (ctrl->cpr_enabled && ctrl->use_hw_closed_loop)
++ cpr3_update_vreg_closed_loop_volt(vreg,
++ vdd_volt, reg_last_measurement);
++
++ if (!vreg->vreg_enabled
++ || vreg->current_corner
++ == CPR3_REGULATOR_CORNER_INVALID) {
++ /* Cannot participate in aggregation. */
++ vreg->aggregated = false;
++ continue;
++ } else {
++ vreg->aggregated = true;
++ thread_valid = true;
++ }
++
++ cpr3_regulator_aggregate_corners(&thread->aggr_corner,
++ &vreg->corner[vreg->current_corner],
++ true, ctrl->step_volt);
++ }
++
++ valid |= thread_valid;
++
++ if (thread_valid)
++ cpr3_regulator_aggregate_corners(&aggr_corner,
++ &thread->aggr_corner,
++ false, ctrl->step_volt);
++ }
++
++ if (valid && ctrl->cpr_allowed_hw && ctrl->cpr_allowed_sw) {
++ rc = cpr3_closed_loop_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
++ return rc;
++ }
++ } else {
++ rc = cpr3_closed_loop_disable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
++ return rc;
++ }
++ }
++
++ /* No threads are enabled with a valid corner so exit. */
++ if (!valid)
++ return 0;
++
++ /*
++ * When using CPR hardware closed-loop, the voltage may vary anywhere
++ * between the floor and ceiling voltage without software notification.
++ * Therefore, it is required that the floor to ceiling range for the
++ * aggregated corner not intersect the APM threshold voltage. Adjust
++ * the floor to ceiling range if this requirement is violated.
++ *
++ * The following algorithm is applied in the case that
++ * floor < threshold <= ceiling:
++ * if open_loop >= threshold - adj, then floor = threshold
++ * else ceiling = threshold - step
++ * where adj = an adjustment factor to ensure sufficient voltage margin
++ * and step = VDD output step size
++ *
++ * The open-loop and last known voltages are also bounded by the new
++ * floor or ceiling value as needed.
++ */
++ if (ctrl->use_hw_closed_loop
++ && aggr_corner.ceiling_volt >= ctrl->apm_threshold_volt
++ && aggr_corner.floor_volt < ctrl->apm_threshold_volt) {
++
++ if (aggr_corner.open_loop_volt
++ >= ctrl->apm_threshold_volt - ctrl->apm_adj_volt)
++ aggr_corner.floor_volt = ctrl->apm_threshold_volt;
++ else
++ aggr_corner.ceiling_volt
++ = ctrl->apm_threshold_volt - ctrl->step_volt;
++
++ aggr_corner.last_volt
++ = max(aggr_corner.last_volt, aggr_corner.floor_volt);
++ aggr_corner.last_volt
++ = min(aggr_corner.last_volt, aggr_corner.ceiling_volt);
++ aggr_corner.open_loop_volt
++ = max(aggr_corner.open_loop_volt, aggr_corner.floor_volt);
++ aggr_corner.open_loop_volt
++ = min(aggr_corner.open_loop_volt, aggr_corner.ceiling_volt);
++ }
++
++ if (ctrl->use_hw_closed_loop
++ && aggr_corner.ceiling_volt >= ctrl->mem_acc_threshold_volt
++ && aggr_corner.floor_volt < ctrl->mem_acc_threshold_volt) {
++ aggr_corner.floor_volt = ctrl->mem_acc_threshold_volt;
++ aggr_corner.last_volt = max(aggr_corner.last_volt,
++ aggr_corner.floor_volt);
++ aggr_corner.open_loop_volt = max(aggr_corner.open_loop_volt,
++ aggr_corner.floor_volt);
++ }
++
++ if (ctrl->use_hw_closed_loop) {
++ dynamic_floor_volt
++ = cpr3_regulator_get_dynamic_floor_volt(ctrl,
++ reg_last_measurement);
++ if (aggr_corner.floor_volt < dynamic_floor_volt) {
++ aggr_corner.floor_volt = dynamic_floor_volt;
++ aggr_corner.last_volt = max(aggr_corner.last_volt,
++ aggr_corner.floor_volt);
++ aggr_corner.open_loop_volt
++ = max(aggr_corner.open_loop_volt,
++ aggr_corner.floor_volt);
++ aggr_corner.ceiling_volt = max(aggr_corner.ceiling_volt,
++ aggr_corner.floor_volt);
++ }
++ }
++
++ if (ctrl->cpr_enabled && ctrl->last_corner_was_closed_loop) {
++ /*
++ * Always program open-loop voltage for CPR4 controllers which
++ * support hardware closed-loop. Storing the last closed loop
++ * voltage in corner structure can still help with debugging.
++ */
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
++ new_volt = aggr_corner.last_volt;
++ else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
++ && ctrl->supports_hw_closed_loop)
++ new_volt = aggr_corner.open_loop_volt;
++ else
++ new_volt = min(aggr_corner.last_volt +
++ cpr3_regulator_max_sdelta_diff(aggr_corner.sdelta,
++ ctrl->step_volt),
++ aggr_corner.ceiling_volt);
++
++ aggr_corner.last_volt = new_volt;
++ } else {
++ new_volt = aggr_corner.open_loop_volt;
++ aggr_corner.last_volt = aggr_corner.open_loop_volt;
++ }
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
++ && ctrl->supports_hw_closed_loop) {
++ /*
++ * Store last aggregated corner open-loop voltage in vdd_volt
++ * which is used when programming current aggregated corner
++ * required voltage.
++ */
++ vdd_volt = last_corner_volt;
++ }
++
++ cpr3_debug(ctrl, "setting new voltage=%d uV\n", new_volt);
++ rc = cpr3_regulator_scale_vdd_voltage(ctrl, new_volt,
++ vdd_volt, &aggr_corner);
++ if (rc) {
++ cpr3_err(ctrl, "vdd voltage scaling failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ /* Only update registers if CPR is enabled. */
++ if (ctrl->cpr_enabled) {
++ if (ctrl->use_hw_closed_loop) {
++ /* Hardware closed-loop */
++
++ /* Set ceiling and floor limits in hardware */
++ rc = regulator_set_voltage(ctrl->vdd_limit_regulator,
++ aggr_corner.floor_volt,
++ aggr_corner.ceiling_volt);
++ if (rc) {
++ cpr3_err(ctrl, "could not configure HW closed-loop voltage limits, rc=%d\n",
++ rc);
++ return rc;
++ }
++ } else {
++ /* Software closed-loop */
++
++ /*
++ * Disable UP or DOWN interrupts when at ceiling or
++ * floor respectively.
++ */
++ if (new_volt == aggr_corner.floor_volt)
++ aggr_corner.irq_en &= ~CPR3_IRQ_DOWN;
++ if (new_volt == aggr_corner.ceiling_volt)
++ aggr_corner.irq_en &= ~CPR3_IRQ_UP;
++
++ cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
++ CPR3_IRQ_UP | CPR3_IRQ_DOWN);
++ cpr3_write(ctrl, CPR3_REG_IRQ_EN, aggr_corner.irq_en);
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ cpr3_regulator_set_target_quot(&ctrl->thread[i]);
++
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++
++ if (vreg->vreg_enabled)
++ vreg->last_closed_loop_corner
++ = vreg->current_corner;
++ }
++ }
++
++ if (ctrl->proc_clock_throttle) {
++ if (aggr_corner.ceiling_volt > aggr_corner.floor_volt
++ && (ctrl->use_hw_closed_loop
++ || new_volt < aggr_corner.ceiling_volt))
++ cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
++ ctrl->proc_clock_throttle);
++ else
++ cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
++ CPR3_PD_THROTTLE_DISABLE);
++ }
++
++ /*
++ * Ensure that all CPR register writes complete before
++ * re-enabling CPR loop operation.
++ */
++ wmb();
++ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
++ && ctrl->vdd_limit_regulator) {
++ /* Set ceiling and floor limits in hardware */
++ rc = regulator_set_voltage(ctrl->vdd_limit_regulator,
++ aggr_corner.floor_volt,
++ aggr_corner.ceiling_volt);
++ if (rc) {
++ cpr3_err(ctrl, "could not configure HW closed-loop voltage limits, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ ctrl->aggr_corner = aggr_corner;
++
++ if (ctrl->allow_core_count_adj || ctrl->allow_temp_adj
++ || ctrl->allow_boost) {
++ rc = cpr3_controller_program_sdelta(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "failed to program sdelta, rc=%d\n", rc);
++ return rc;
++ }
++ }
++
++ /*
++ * Only enable the CPR controller if it is possible to set more than
++ * one vdd-supply voltage.
++ */
++ if (aggr_corner.ceiling_volt > aggr_corner.floor_volt &&
++ !aggr_corner.use_open_loop)
++ cpr3_ctrl_loop_enable(ctrl);
++
++ ctrl->last_corner_was_closed_loop = ctrl->cpr_enabled;
++ cpr3_debug(ctrl, "CPR configuration updated\n");
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_wait_for_idle() - wait for the CPR controller to no longer be
++ * busy
++ * @ctrl: Pointer to the CPR3 controller
++ * @max_wait_ns: Max wait time in nanoseconds
++ *
++ * Return: 0 on success or -ETIMEDOUT if the controller was still busy after
++ * the maximum delay time
++ */
++static int cpr3_regulator_wait_for_idle(struct cpr3_controller *ctrl,
++ s64 max_wait_ns)
++{
++ ktime_t start, end;
++ s64 time_ns;
++ u32 reg;
++
++ /*
++ * Ensure that all previous CPR register writes have completed before
++ * checking the status register.
++ */
++ mb();
++
++ start = ktime_get();
++ do {
++ end = ktime_get();
++ time_ns = ktime_to_ns(ktime_sub(end, start));
++ if (time_ns > max_wait_ns) {
++ cpr3_err(ctrl, "CPR controller still busy after %lld us\n",
++ div_s64(time_ns, 1000));
++ return -ETIMEDOUT;
++ }
++ usleep_range(50, 100);
++ reg = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
++ } while (reg & CPR3_CPR_STATUS_BUSY_MASK);
++
++ return 0;
++}
++
++/**
++ * cmp_int() - int comparison function to be passed into the sort() function
++ * which leads to ascending sorting
++ * @a: First int value
++ * @b: Second int value
++ *
++ * Return: >0 if a > b, 0 if a == b, <0 if a < b
++ */
++static int cmp_int(const void *a, const void *b)
++{
++ return *(int *)a - *(int *)b;
++}
++
++/**
++ * cpr3_regulator_measure_aging() - measure the quotient difference for the
++ * specified CPR aging sensor
++ * @ctrl: Pointer to the CPR3 controller
++ * @aging_sensor: Aging sensor to measure
++ *
++ * Note that vdd-supply must be configured to the aging reference voltage before
++ * calling this function.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_measure_aging(struct cpr3_controller *ctrl,
++ struct cpr3_aging_sensor_info *aging_sensor)
++{
++ u32 mask, reg, result, quot_min, quot_max, sel_min, sel_max;
++ u32 quot_min_scaled, quot_max_scaled;
++ u32 gcnt, gcnt_ref, gcnt0_restore, gcnt1_restore, irq_restore;
++ u32 ro_mask_restore, cont_dly_restore, up_down_dly_restore = 0;
++ int quot_delta, quot_delta_scaled, quot_delta_scaled_sum;
++ int *quot_delta_results;
++ int rc, rc2, i, aging_measurement_count, filtered_count;
++ bool is_aging_measurement;
++
++ quot_delta_results = kcalloc(CPR3_AGING_MEASUREMENT_ITERATIONS,
++ sizeof(*quot_delta_results), GFP_KERNEL);
++ if (!quot_delta_results)
++ return -ENOMEM;
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc);
++ kfree(quot_delta_results);
++ return rc;
++ }
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ /* Enable up, down, and mid CPR interrupts */
++ irq_restore = cpr3_read(ctrl, CPR3_REG_IRQ_EN);
++ cpr3_write(ctrl, CPR3_REG_IRQ_EN,
++ CPR3_IRQ_UP | CPR3_IRQ_DOWN | CPR3_IRQ_MID);
++
++ /* Ensure that the aging sensor is assigned to CPR thread 0 */
++ cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(aging_sensor->sensor_id), 0);
++
++ /* Switch from HW to SW closed-loop if necessary */
++ if (ctrl->supports_hw_closed_loop) {
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
++ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
++ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
++ CPR3_HW_CLOSED_LOOP_DISABLE);
++ }
++ }
++
++ /* Configure the GCNT for RO0 and RO1 that are used for aging */
++ gcnt0_restore = cpr3_read(ctrl, CPR3_REG_GCNT(0));
++ gcnt1_restore = cpr3_read(ctrl, CPR3_REG_GCNT(1));
++ gcnt_ref = cpr3_regulator_get_gcnt(ctrl);
++ gcnt = gcnt_ref * 3 / 2;
++ cpr3_write(ctrl, CPR3_REG_GCNT(0), gcnt);
++ cpr3_write(ctrl, CPR3_REG_GCNT(1), gcnt);
++
++ /* Unmask all RO's */
++ ro_mask_restore = cpr3_read(ctrl, CPR3_REG_RO_MASK(0));
++ cpr3_write(ctrl, CPR3_REG_RO_MASK(0), 0);
++
++ /*
++ * Mask all sensors except for the one to measure and bypass all
++ * sensors in collapsible domains.
++ */
++ for (i = 0; i <= ctrl->sensor_count / 32; i++) {
++ mask = GENMASK(min(31, ctrl->sensor_count - i * 32), 0);
++ if (aging_sensor->sensor_id / 32 >= i
++ && aging_sensor->sensor_id / 32 < (i + 1))
++ mask &= ~BIT(aging_sensor->sensor_id % 32);
++ cpr3_write(ctrl, CPR3_REG_SENSOR_MASK_WRITE_BANK(i), mask);
++ cpr3_write(ctrl, CPR3_REG_SENSOR_BYPASS_WRITE_BANK(i),
++ aging_sensor->bypass_mask[i]);
++ }
++
++ /* Set CPR loop delays to 0 us */
++ if (ctrl->supports_hw_closed_loop
++ && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ cont_dly_restore = cpr3_read(ctrl, CPR3_REG_CPR_TIMER_MID_CONT);
++ up_down_dly_restore = cpr3_read(ctrl,
++ CPR3_REG_CPR_TIMER_UP_DN_CONT);
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, 0);
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT, 0);
++ } else {
++ cont_dly_restore = cpr3_read(ctrl,
++ CPR3_REG_CPR_TIMER_AUTO_CONT);
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT, 0);
++ }
++
++ /* Set count mode to all-at-once min with no repeat */
++ cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
++ CPR3_CPR_CTL_COUNT_MODE_MASK | CPR3_CPR_CTL_COUNT_REPEAT_MASK,
++ CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN
++ << CPR3_CPR_CTL_COUNT_MODE_SHIFT);
++
++ cpr3_ctrl_loop_enable(ctrl);
++
++ rc = cpr3_regulator_wait_for_idle(ctrl,
++ CPR3_AGING_MEASUREMENT_TIMEOUT_NS);
++ if (rc)
++ goto cleanup;
++
++ /* Set count mode to all-at-once aging */
++ cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL, CPR3_CPR_CTL_COUNT_MODE_MASK,
++ CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE
++ << CPR3_CPR_CTL_COUNT_MODE_SHIFT);
++
++ aging_measurement_count = 0;
++ for (i = 0; i < CPR3_AGING_MEASUREMENT_ITERATIONS; i++) {
++ /* Send CONT_NACK */
++ cpr3_write(ctrl, CPR3_REG_CONT_CMD, CPR3_CONT_CMD_NACK);
++
++ rc = cpr3_regulator_wait_for_idle(ctrl,
++ CPR3_AGING_MEASUREMENT_TIMEOUT_NS);
++ if (rc)
++ goto cleanup;
++
++ /* Check for PAGE_IS_AGE flag in status register */
++ reg = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
++ is_aging_measurement
++ = reg & CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK;
++
++ /* Read CPR measurement results */
++ result = cpr3_read(ctrl, CPR3_REG_RESULT1(0));
++ quot_min = (result & CPR3_RESULT1_QUOT_MIN_MASK)
++ >> CPR3_RESULT1_QUOT_MIN_SHIFT;
++ quot_max = (result & CPR3_RESULT1_QUOT_MAX_MASK)
++ >> CPR3_RESULT1_QUOT_MAX_SHIFT;
++ sel_min = (result & CPR3_RESULT1_RO_MIN_MASK)
++ >> CPR3_RESULT1_RO_MIN_SHIFT;
++ sel_max = (result & CPR3_RESULT1_RO_MAX_MASK)
++ >> CPR3_RESULT1_RO_MAX_SHIFT;
++
++ /*
++ * Scale the quotients so that they are equivalent to the fused
++ * values. This accounts for the difference in measurement
++ * interval times.
++ */
++ quot_min_scaled = quot_min * (gcnt_ref + 1) / (gcnt + 1);
++ quot_max_scaled = quot_max * (gcnt_ref + 1) / (gcnt + 1);
++
++ if (sel_max == 1) {
++ quot_delta = quot_max - quot_min;
++ quot_delta_scaled = quot_max_scaled - quot_min_scaled;
++ } else {
++ quot_delta = quot_min - quot_max;
++ quot_delta_scaled = quot_min_scaled - quot_max_scaled;
++ }
++
++ if (is_aging_measurement)
++ quot_delta_results[aging_measurement_count++]
++ = quot_delta_scaled;
++
++ cpr3_debug(ctrl, "aging results: page_is_age=%u, sel_min=%u, sel_max=%u, quot_min=%u, quot_max=%u, quot_delta=%d, quot_min_scaled=%u, quot_max_scaled=%u, quot_delta_scaled=%d\n",
++ is_aging_measurement, sel_min, sel_max, quot_min,
++ quot_max, quot_delta, quot_min_scaled, quot_max_scaled,
++ quot_delta_scaled);
++ }
++
++ filtered_count
++ = aging_measurement_count - CPR3_AGING_MEASUREMENT_FILTER * 2;
++ if (filtered_count > 0) {
++ sort(quot_delta_results, aging_measurement_count,
++ sizeof(*quot_delta_results), cmp_int, NULL);
++
++ quot_delta_scaled_sum = 0;
++ for (i = 0; i < filtered_count; i++)
++ quot_delta_scaled_sum
++ += quot_delta_results[i
++ + CPR3_AGING_MEASUREMENT_FILTER];
++
++ aging_sensor->measured_quot_diff
++ = quot_delta_scaled_sum / filtered_count;
++ cpr3_info(ctrl, "average quotient delta=%d (count=%d)\n",
++ aging_sensor->measured_quot_diff,
++ filtered_count);
++ } else {
++ cpr3_err(ctrl, "%d aging measurements completed after %d iterations\n",
++ aging_measurement_count,
++ CPR3_AGING_MEASUREMENT_ITERATIONS);
++ rc = -EBUSY;
++ }
++
++cleanup:
++ kfree(quot_delta_results);
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc2 = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc2) {
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc2);
++ rc = rc2;
++ }
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_restore);
++
++ cpr3_write(ctrl, CPR3_REG_RO_MASK(0), ro_mask_restore);
++
++ cpr3_write(ctrl, CPR3_REG_GCNT(0), gcnt0_restore);
++ cpr3_write(ctrl, CPR3_REG_GCNT(1), gcnt1_restore);
++
++ if (ctrl->supports_hw_closed_loop
++ && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_MID_CONT, cont_dly_restore);
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_UP_DN_CONT,
++ up_down_dly_restore);
++ } else {
++ cpr3_write(ctrl, CPR3_REG_CPR_TIMER_AUTO_CONT,
++ cont_dly_restore);
++ }
++
++ for (i = 0; i <= ctrl->sensor_count / 32; i++) {
++ cpr3_write(ctrl, CPR3_REG_SENSOR_MASK_WRITE_BANK(i), 0);
++ cpr3_write(ctrl, CPR3_REG_SENSOR_BYPASS_WRITE_BANK(i), 0);
++ }
++
++ cpr3_masked_write(ctrl, CPR3_REG_CPR_CTL,
++ CPR3_CPR_CTL_COUNT_MODE_MASK | CPR3_CPR_CTL_COUNT_REPEAT_MASK,
++ (ctrl->count_mode << CPR3_CPR_CTL_COUNT_MODE_SHIFT)
++ | (ctrl->count_repeat << CPR3_CPR_CTL_COUNT_REPEAT_SHIFT));
++
++ cpr3_write(ctrl, CPR3_REG_SENSOR_OWNER(aging_sensor->sensor_id),
++ ctrl->sensor_owner[aging_sensor->sensor_id]);
++
++ cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
++ CPR3_IRQ_UP | CPR3_IRQ_DOWN | CPR3_IRQ_MID);
++
++ if (ctrl->supports_hw_closed_loop) {
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
++ ctrl->use_hw_closed_loop
++ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
++ : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
++ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
++ ctrl->use_hw_closed_loop
++ ? CPR3_HW_CLOSED_LOOP_ENABLE
++ : CPR3_HW_CLOSED_LOOP_DISABLE);
++ }
++ }
++
++ return rc;
++}
++
++/**
++ * cpr3_regulator_readjust_volt_and_quot() - readjust the target quotients as
++ * well as the floor, ceiling, and open-loop voltages for the
++ * regulator by removing the old adjustment and adding the new one
++ * @vreg: Pointer to the CPR3 regulator
++ * @old_adjust_volt: Old aging adjustment voltage in microvolts
++ * @new_adjust_volt: New aging adjustment voltage in microvolts
++ *
++ * Also reset the cached closed loop voltage (last_volt) to equal the open-loop
++ * voltage for each corner.
++ *
++ * Return: None
++ */
++static void cpr3_regulator_readjust_volt_and_quot(struct cpr3_regulator *vreg,
++ int old_adjust_volt, int new_adjust_volt)
++{
++ unsigned long long temp;
++ int i, j, old_volt, new_volt, rounded_volt;
++
++ if (!vreg->aging_allowed)
++ return;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ temp = (unsigned long long)old_adjust_volt
++ * (unsigned long long)vreg->corner[i].aging_derate;
++ do_div(temp, 1000);
++ old_volt = temp;
++
++ temp = (unsigned long long)new_adjust_volt
++ * (unsigned long long)vreg->corner[i].aging_derate;
++ do_div(temp, 1000);
++ new_volt = temp;
++
++ old_volt = min(vreg->aging_max_adjust_volt, old_volt);
++ new_volt = min(vreg->aging_max_adjust_volt, new_volt);
++
++ for (j = 0; j < CPR3_RO_COUNT; j++) {
++ if (vreg->corner[i].target_quot[j] != 0) {
++ vreg->corner[i].target_quot[j]
++ += cpr3_quot_adjustment(
++ vreg->corner[i].ro_scale[j],
++ new_volt)
++ - cpr3_quot_adjustment(
++ vreg->corner[i].ro_scale[j],
++ old_volt);
++ }
++ }
++
++ rounded_volt = CPR3_ROUND(new_volt,
++ vreg->thread->ctrl->step_volt);
++
++ if (!vreg->aging_allow_open_loop_adj)
++ rounded_volt = 0;
++
++ vreg->corner[i].ceiling_volt
++ = vreg->corner[i].unaged_ceiling_volt + rounded_volt;
++ vreg->corner[i].ceiling_volt = min(vreg->corner[i].ceiling_volt,
++ vreg->corner[i].abs_ceiling_volt);
++ vreg->corner[i].floor_volt
++ = vreg->corner[i].unaged_floor_volt + rounded_volt;
++ vreg->corner[i].floor_volt = min(vreg->corner[i].floor_volt,
++ vreg->corner[i].ceiling_volt);
++ vreg->corner[i].open_loop_volt
++ = vreg->corner[i].unaged_open_loop_volt + rounded_volt;
++ vreg->corner[i].open_loop_volt
++ = min(vreg->corner[i].open_loop_volt,
++ vreg->corner[i].ceiling_volt);
++
++ vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
++
++ cpr3_debug(vreg, "corner %d: applying %d uV closed-loop and %d uV open-loop voltage margin adjustment\n",
++ i, new_volt, rounded_volt);
++ }
++}
++
++/**
++ * cpr3_regulator_set_aging_ref_adjustment() - adjust target quotients for the
++ * regulators managed by this CPR controller to account for aging
++ * @ctrl: Pointer to the CPR3 controller
++ * @ref_adjust_volt: New aging reference adjustment voltage in microvolts to
++ * apply to all regulators managed by this CPR controller
++ *
++ * The existing aging adjustment as defined by ctrl->aging_ref_adjust_volt is
++ * first removed and then the adjustment is applied. Lastly, the value of
++ * ctrl->aging_ref_adjust_volt is updated to ref_adjust_volt.
++ */
++static void cpr3_regulator_set_aging_ref_adjustment(
++ struct cpr3_controller *ctrl, int ref_adjust_volt)
++{
++ struct cpr3_regulator *vreg;
++ int i, j;
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ cpr3_regulator_readjust_volt_and_quot(vreg,
++ ctrl->aging_ref_adjust_volt, ref_adjust_volt);
++ }
++ }
++
++ ctrl->aging_ref_adjust_volt = ref_adjust_volt;
++}
++
++/**
++ * cpr3_regulator_aging_adjust() - adjust the target quotients for regulators
++ * based on the output of CPR aging sensors
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_aging_adjust(struct cpr3_controller *ctrl)
++{
++ struct cpr3_regulator *vreg;
++ struct cpr3_corner restore_aging_corner;
++ struct cpr3_corner *corner;
++ int *restore_current_corner;
++ bool *restore_vreg_enabled;
++ int i, j, id, rc, rc2, vreg_count, aging_volt, max_aging_volt = 0;
++ u32 reg;
++
++ if (!ctrl->aging_required || !ctrl->cpr_enabled
++ || ctrl->aggr_corner.ceiling_volt == 0
++ || ctrl->aggr_corner.ceiling_volt > ctrl->aging_ref_volt)
++ return 0;
++
++ for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ vreg_count++;
++
++ if (vreg->aging_allowed && vreg->vreg_enabled
++ && vreg->current_corner > vreg->aging_corner)
++ return 0;
++ }
++ }
++
++ /* Verify that none of the aging sensors are currently masked. */
++ for (i = 0; i < ctrl->aging_sensor_count; i++) {
++ id = ctrl->aging_sensor[i].sensor_id;
++ reg = cpr3_read(ctrl, CPR3_REG_SENSOR_MASK_READ(id));
++ if (reg & BIT(id % 32))
++ return 0;
++ }
++
++ /*
++ * Verify that the aging possible register (if specified) has an
++ * acceptable value.
++ */
++ if (ctrl->aging_possible_reg) {
++ reg = readl_relaxed(ctrl->aging_possible_reg);
++ reg &= ctrl->aging_possible_mask;
++ if (reg != ctrl->aging_possible_val)
++ return 0;
++ }
++
++ restore_current_corner = kcalloc(vreg_count,
++ sizeof(*restore_current_corner), GFP_KERNEL);
++ restore_vreg_enabled = kcalloc(vreg_count,
++ sizeof(*restore_vreg_enabled), GFP_KERNEL);
++ if (!restore_current_corner || !restore_vreg_enabled) {
++ kfree(restore_current_corner);
++ kfree(restore_vreg_enabled);
++ return -ENOMEM;
++ }
++
++ /* Force all regulators to the aging corner */
++ for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++, vreg_count++) {
++ vreg = &ctrl->thread[i].vreg[j];
++
++ restore_current_corner[vreg_count]
++ = vreg->current_corner;
++ restore_vreg_enabled[vreg_count]
++ = vreg->vreg_enabled;
++
++ vreg->current_corner = vreg->aging_corner;
++ vreg->vreg_enabled = true;
++ }
++ }
++
++ /* Force one of the regulators to require the aging reference voltage */
++ vreg = &ctrl->thread[0].vreg[0];
++ corner = &vreg->corner[vreg->current_corner];
++ restore_aging_corner = *corner;
++ corner->ceiling_volt = ctrl->aging_ref_volt;
++ corner->floor_volt = ctrl->aging_ref_volt;
++ corner->open_loop_volt = ctrl->aging_ref_volt;
++ corner->last_volt = ctrl->aging_ref_volt;
++
++ /* Skip last_volt caching */
++ ctrl->last_corner_was_closed_loop = false;
++
++ /* Set the vdd supply voltage to the aging reference voltage */
++ rc = _cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "unable to force vdd-supply to the aging reference voltage=%d uV, rc=%d\n",
++ ctrl->aging_ref_volt, rc);
++ goto cleanup;
++ }
++
++ if (ctrl->aging_vdd_mode) {
++ rc = regulator_set_mode(ctrl->vdd_regulator,
++ ctrl->aging_vdd_mode);
++ if (rc) {
++ cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
++ ctrl->aging_vdd_mode, rc);
++ goto cleanup;
++ }
++ }
++
++ /* Perform aging measurement on all aging sensors */
++ for (i = 0; i < ctrl->aging_sensor_count; i++) {
++ for (j = 0; j < CPR3_AGING_RETRY_COUNT; j++) {
++ rc = cpr3_regulator_measure_aging(ctrl,
++ &ctrl->aging_sensor[i]);
++ if (!rc)
++ break;
++ }
++
++ if (!rc) {
++ aging_volt =
++ cpr3_voltage_adjustment(
++ ctrl->aging_sensor[i].ro_scale,
++ ctrl->aging_sensor[i].measured_quot_diff
++ - ctrl->aging_sensor[i].init_quot_diff);
++ max_aging_volt = max(max_aging_volt, aging_volt);
++ } else {
++ cpr3_err(ctrl, "CPR aging measurement failed after %d tries, rc=%d\n",
++ j, rc);
++ ctrl->aging_failed = true;
++ ctrl->aging_required = false;
++ goto cleanup;
++ }
++ }
++
++cleanup:
++ vreg = &ctrl->thread[0].vreg[0];
++ vreg->corner[vreg->current_corner] = restore_aging_corner;
++
++ for (i = 0, vreg_count = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++, vreg_count++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ vreg->current_corner
++ = restore_current_corner[vreg_count];
++ vreg->vreg_enabled = restore_vreg_enabled[vreg_count];
++ }
++ }
++
++ kfree(restore_current_corner);
++ kfree(restore_vreg_enabled);
++
++ /* Adjust the CPR target quotients according to the aging measurement */
++ if (!rc) {
++ cpr3_regulator_set_aging_ref_adjustment(ctrl, max_aging_volt);
++
++ cpr3_info(ctrl, "aging measurement successful; aging reference adjustment voltage=%d uV\n",
++ ctrl->aging_ref_adjust_volt);
++ ctrl->aging_succeeded = true;
++ ctrl->aging_required = false;
++ }
++
++ if (ctrl->aging_complete_vdd_mode) {
++ rc = regulator_set_mode(ctrl->vdd_regulator,
++ ctrl->aging_complete_vdd_mode);
++ if (rc)
++ cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
++ ctrl->aging_complete_vdd_mode, rc);
++ }
++
++ /* Skip last_volt caching */
++ ctrl->last_corner_was_closed_loop = false;
++
++ /*
++ * Restore vdd-supply to the voltage before the aging measurement and
++ * restore the CPR3 controller hardware state.
++ */
++ rc2 = _cpr3_regulator_update_ctrl_state(ctrl);
++
++ /* Stop last_volt caching on for the next request */
++ ctrl->last_corner_was_closed_loop = false;
++
++ return rc ? rc : rc2;
++}
++
++/**
++ * cpr3_regulator_update_ctrl_state() - update the state of the CPR controller
++ * to reflect the corners used by all CPR3 regulators as well as
++ * the CPR operating mode and perform aging adjustments if needed
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Note, CPR3 controller lock must be held by the caller.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ rc = _cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc)
++ return rc;
++
++ return cpr3_regulator_aging_adjust(ctrl);
++}
++
++/**
++ * cpr3_regulator_set_voltage() - set the voltage corner for the CPR3 regulator
++ * associated with the regulator device
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ * @corner: New voltage corner to set (offset by CPR3_CORNER_OFFSET)
++ * @corner_max: Maximum voltage corner allowed (offset by
++ * CPR3_CORNER_OFFSET)
++ * @selector: Pointer which is filled with the selector value for the
++ * corner
++ *
++ * This function is passed as a callback function into the regulator ops that
++ * are registered for each cpr3-regulator device. The VDD voltage will not be
++ * physically configured until both this function and cpr3_regulator_enable()
++ * are called.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_set_voltage(struct regulator_dev *rdev,
++ int corner, int corner_max, unsigned *selector)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ int rc = 0;
++ int last_corner;
++
++ corner -= CPR3_CORNER_OFFSET;
++ corner_max -= CPR3_CORNER_OFFSET;
++ *selector = corner;
++
++ mutex_lock(&ctrl->lock);
++
++ if (!vreg->vreg_enabled) {
++ vreg->current_corner = corner;
++ cpr3_debug(vreg, "stored corner=%d\n", corner);
++ goto done;
++ } else if (vreg->current_corner == corner) {
++ goto done;
++ }
++
++ last_corner = vreg->current_corner;
++ vreg->current_corner = corner;
++
++ if (vreg->cpr4_regulator_data != NULL)
++ if (vreg->cpr4_regulator_data->mem_acc_funcs != NULL)
++ vreg->cpr4_regulator_data->mem_acc_funcs->set_mem_acc(rdev);
++
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
++ vreg->current_corner = last_corner;
++ }
++
++ if (vreg->cpr4_regulator_data != NULL)
++ if (vreg->cpr4_regulator_data->mem_acc_funcs != NULL)
++ vreg->cpr4_regulator_data->mem_acc_funcs->clear_mem_acc(rdev);
++
++ cpr3_debug(vreg, "set corner=%d\n", corner);
++done:
++ mutex_unlock(&ctrl->lock);
++
++ return rc;
++}
++
++/**
++ * cpr3_handle_temp_open_loop_adjustment() - voltage based cold temperature
++ *
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ * @is_cold: Flag to denote enter/exit cold condition
++ *
++ * This function is adjusts voltage margin based on cold condition
++ *
++ * Return: 0 = success
++ */
++
++int cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
++ bool is_cold)
++{
++ int i ,j, k, rc;
++ struct cpr3_regulator *vreg;
++
++ mutex_lock(&ctrl->lock);
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ for (k = 0; k < vreg->corner_count; k++) {
++ vreg->corner[k].open_loop_volt = is_cold ?
++ vreg->corner[k].cold_temp_open_loop_volt :
++ vreg->corner[k].normal_temp_open_loop_volt;
++ }
++ }
++ }
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ mutex_unlock(&ctrl->lock);
++
++ return rc;
++}
++
++/**
++ * cpr3_regulator_get_voltage() - get the voltage corner for the CPR3 regulator
++ * associated with the regulator device
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ *
++ * This function is passed as a callback function into the regulator ops that
++ * are registered for each cpr3-regulator device.
++ *
++ * Return: voltage corner value offset by CPR3_CORNER_OFFSET
++ */
++static int cpr3_regulator_get_voltage(struct regulator_dev *rdev)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++
++ if (vreg->current_corner == CPR3_REGULATOR_CORNER_INVALID)
++ return CPR3_CORNER_OFFSET;
++ else
++ return vreg->current_corner + CPR3_CORNER_OFFSET;
++}
++
++/**
++ * cpr3_regulator_list_voltage() - return the voltage corner mapped to the
++ * specified selector
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ * @selector: Regulator selector
++ *
++ * This function is passed as a callback function into the regulator ops that
++ * are registered for each cpr3-regulator device.
++ *
++ * Return: voltage corner value offset by CPR3_CORNER_OFFSET
++ */
++static int cpr3_regulator_list_voltage(struct regulator_dev *rdev,
++ unsigned selector)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++
++ if (selector < vreg->corner_count)
++ return selector + CPR3_CORNER_OFFSET;
++ else
++ return 0;
++}
++
++/**
++ * cpr3_regulator_is_enabled() - return the enable state of the CPR3 regulator
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ *
++ * This function is passed as a callback function into the regulator ops that
++ * are registered for each cpr3-regulator device.
++ *
++ * Return: true if regulator is enabled, false if regulator is disabled
++ */
++static int cpr3_regulator_is_enabled(struct regulator_dev *rdev)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++
++ return vreg->vreg_enabled;
++}
++
++/**
++ * cpr3_regulator_enable() - enable the CPR3 regulator
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ *
++ * This function is passed as a callback function into the regulator ops that
++ * are registered for each cpr3-regulator device.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_enable(struct regulator_dev *rdev)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ int rc = 0;
++
++ if (vreg->vreg_enabled == true)
++ return 0;
++
++ mutex_lock(&ctrl->lock);
++
++ if (ctrl->system_regulator) {
++ rc = regulator_enable(ctrl->system_regulator);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_enable(system) failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ rc = regulator_enable(ctrl->vdd_regulator);
++ if (rc) {
++ cpr3_err(vreg, "regulator_enable(vdd) failed, rc=%d\n", rc);
++ goto done;
++ }
++
++ vreg->vreg_enabled = true;
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
++ regulator_disable(ctrl->vdd_regulator);
++ vreg->vreg_enabled = false;
++ goto done;
++ }
++
++ cpr3_debug(vreg, "Enabled\n");
++done:
++ mutex_unlock(&ctrl->lock);
++
++ return rc;
++}
++
++/**
++ * cpr3_regulator_disable() - disable the CPR3 regulator
++ * @rdev: Regulator device pointer for the cpr3-regulator
++ *
++ * This function is passed as a callback function into the regulator ops that
++ * are registered for each cpr3-regulator device.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_disable(struct regulator_dev *rdev)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ int rc, rc2;
++
++ if (vreg->vreg_enabled == false)
++ return 0;
++
++ mutex_lock(&ctrl->lock);
++ rc = regulator_disable(ctrl->vdd_regulator);
++ if (rc) {
++ cpr3_err(vreg, "regulator_disable(vdd) failed, rc=%d\n", rc);
++ goto done;
++ }
++
++ vreg->vreg_enabled = false;
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(vreg, "could not update CPR state, rc=%d\n", rc);
++ rc2 = regulator_enable(ctrl->vdd_regulator);
++ vreg->vreg_enabled = true;
++ goto done;
++ }
++
++ if (ctrl->system_regulator) {
++ rc = regulator_disable(ctrl->system_regulator);
++ if (rc) {
++ cpr3_err(ctrl, "regulator_disable(system) failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ cpr3_debug(vreg, "Disabled\n");
++done:
++ mutex_unlock(&ctrl->lock);
++
++ return rc;
++}
++
++static struct regulator_ops cpr3_regulator_ops = {
++ .enable = cpr3_regulator_enable,
++ .disable = cpr3_regulator_disable,
++ .is_enabled = cpr3_regulator_is_enabled,
++ .set_voltage = cpr3_regulator_set_voltage,
++ .get_voltage = cpr3_regulator_get_voltage,
++ .list_voltage = cpr3_regulator_list_voltage,
++};
++
++/**
++ * cpr3_print_result() - print CPR measurement results to the kernel log for
++ * debugging purposes
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: None
++ */
++static void cpr3_print_result(struct cpr3_thread *thread)
++{
++ struct cpr3_controller *ctrl = thread->ctrl;
++ u32 result[3], busy, step_dn, step_up, error_steps, error, negative;
++ u32 quot_min, quot_max, ro_min, ro_max, step_quot_min, step_quot_max;
++ u32 sensor_min, sensor_max;
++ char *sign;
++
++ result[0] = cpr3_read(ctrl, CPR3_REG_RESULT0(thread->thread_id));
++ result[1] = cpr3_read(ctrl, CPR3_REG_RESULT1(thread->thread_id));
++ result[2] = cpr3_read(ctrl, CPR3_REG_RESULT2(thread->thread_id));
++
++ busy = !!(result[0] & CPR3_RESULT0_BUSY_MASK);
++ step_dn = !!(result[0] & CPR3_RESULT0_STEP_DN_MASK);
++ step_up = !!(result[0] & CPR3_RESULT0_STEP_UP_MASK);
++ error_steps = (result[0] & CPR3_RESULT0_ERROR_STEPS_MASK)
++ >> CPR3_RESULT0_ERROR_STEPS_SHIFT;
++ error = (result[0] & CPR3_RESULT0_ERROR_MASK)
++ >> CPR3_RESULT0_ERROR_SHIFT;
++ negative = !!(result[0] & CPR3_RESULT0_NEGATIVE_MASK);
++
++ quot_min = (result[1] & CPR3_RESULT1_QUOT_MIN_MASK)
++ >> CPR3_RESULT1_QUOT_MIN_SHIFT;
++ quot_max = (result[1] & CPR3_RESULT1_QUOT_MAX_MASK)
++ >> CPR3_RESULT1_QUOT_MAX_SHIFT;
++ ro_min = (result[1] & CPR3_RESULT1_RO_MIN_MASK)
++ >> CPR3_RESULT1_RO_MIN_SHIFT;
++ ro_max = (result[1] & CPR3_RESULT1_RO_MAX_MASK)
++ >> CPR3_RESULT1_RO_MAX_SHIFT;
++
++ step_quot_min = (result[2] & CPR3_RESULT2_STEP_QUOT_MIN_MASK)
++ >> CPR3_RESULT2_STEP_QUOT_MIN_SHIFT;
++ step_quot_max = (result[2] & CPR3_RESULT2_STEP_QUOT_MAX_MASK)
++ >> CPR3_RESULT2_STEP_QUOT_MAX_SHIFT;
++ sensor_min = (result[2] & CPR3_RESULT2_SENSOR_MIN_MASK)
++ >> CPR3_RESULT2_SENSOR_MIN_SHIFT;
++ sensor_max = (result[2] & CPR3_RESULT2_SENSOR_MAX_MASK)
++ >> CPR3_RESULT2_SENSOR_MAX_SHIFT;
++
++ sign = negative ? "-" : "";
++ cpr3_debug(ctrl, "thread %u: busy=%u, step_dn=%u, step_up=%u, error_steps=%s%u, error=%s%u\n",
++ thread->thread_id, busy, step_dn, step_up, sign, error_steps,
++ sign, error);
++ cpr3_debug(ctrl, "thread %u: quot_min=%u, quot_max=%u, ro_min=%u, ro_max=%u\n",
++ thread->thread_id, quot_min, quot_max, ro_min, ro_max);
++ cpr3_debug(ctrl, "thread %u: step_quot_min=%u, step_quot_max=%u, sensor_min=%u, sensor_max=%u\n",
++ thread->thread_id, step_quot_min, step_quot_max, sensor_min,
++ sensor_max);
++}
++
++/**
++ * cpr3_thread_busy() - returns if the specified CPR3 thread is busy taking
++ * a measurement
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: CPR3 busy status
++ */
++static bool cpr3_thread_busy(struct cpr3_thread *thread)
++{
++ u32 result;
++
++ result = cpr3_read(thread->ctrl, CPR3_REG_RESULT0(thread->thread_id));
++
++ return !!(result & CPR3_RESULT0_BUSY_MASK);
++}
++
++/**
++ * cpr3_irq_handler() - CPR interrupt handler callback function used for
++ * software closed-loop operation
++ * @irq: CPR interrupt number
++ * @data: Private data corresponding to the CPR3 controller
++ * pointer
++ *
++ * This function increases or decreases the vdd supply voltage based upon the
++ * CPR controller recommendation.
++ *
++ * Return: IRQ_HANDLED
++ */
++static irqreturn_t cpr3_irq_handler(int irq, void *data)
++{
++ struct cpr3_controller *ctrl = data;
++ struct cpr3_corner *aggr = &ctrl->aggr_corner;
++ u32 cont = CPR3_CONT_CMD_NACK;
++ u32 reg_last_measurement = 0;
++ struct cpr3_regulator *vreg;
++ struct cpr3_corner *corner;
++ unsigned long flags;
++ int i, j, new_volt, last_volt, dynamic_floor_volt, rc;
++ u32 irq_en, status, cpr_status, ctl;
++ bool up, down;
++
++ mutex_lock(&ctrl->lock);
++
++ if (!ctrl->cpr_enabled) {
++ cpr3_debug(ctrl, "CPR interrupt received but CPR is disabled\n");
++ mutex_unlock(&ctrl->lock);
++ return IRQ_HANDLED;
++ } else if (ctrl->use_hw_closed_loop) {
++ cpr3_debug(ctrl, "CPR interrupt received but CPR is using HW closed-loop\n");
++ goto done;
++ }
++
++ /*
++ * CPR IRQ status checking and CPR controller disabling must happen
++ * atomically and without invening delay in order to avoid an interrupt
++ * storm caused by the handler racing with the CPR controller.
++ */
++ local_irq_save(flags);
++ preempt_disable();
++
++ status = cpr3_read(ctrl, CPR3_REG_IRQ_STATUS);
++ up = status & CPR3_IRQ_UP;
++ down = status & CPR3_IRQ_DOWN;
++
++ if (!up && !down) {
++ /*
++ * Toggle the CPR controller off and then back on since the
++ * hardware and software states are out of sync. This condition
++ * occurs after an aging measurement completes as the CPR IRQ
++ * physically triggers during the aging measurement but the
++ * handler is stuck waiting on the mutex lock.
++ */
++ cpr3_ctrl_loop_disable(ctrl);
++
++ local_irq_restore(flags);
++ preempt_enable();
++
++ /* Wait for the loop disable write to complete */
++ mb();
++
++ /* Wait for BUSY=1 and LOOP_EN=0 in CPR controller registers. */
++ for (i = 0; i < CPR3_REGISTER_WRITE_DELAY_US / 10; i++) {
++ cpr_status = cpr3_read(ctrl, CPR3_REG_CPR_STATUS);
++ ctl = cpr3_read(ctrl, CPR3_REG_CPR_CTL);
++ if (cpr_status & CPR3_CPR_STATUS_BUSY_MASK
++ && (ctl & CPR3_CPR_CTL_LOOP_EN_MASK)
++ == CPR3_CPR_CTL_LOOP_DISABLE)
++ break;
++ udelay(10);
++ }
++ if (i == CPR3_REGISTER_WRITE_DELAY_US / 10)
++ cpr3_debug(ctrl, "CPR controller not disabled after %d us\n",
++ CPR3_REGISTER_WRITE_DELAY_US);
++
++ /* Clear interrupt status */
++ cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR,
++ CPR3_IRQ_UP | CPR3_IRQ_DOWN);
++
++ /* Wait for the interrupt clearing write to complete */
++ mb();
++
++ /* Wait for IRQ_STATUS register to be cleared. */
++ for (i = 0; i < CPR3_REGISTER_WRITE_DELAY_US / 10; i++) {
++ status = cpr3_read(ctrl, CPR3_REG_IRQ_STATUS);
++ if (!(status & (CPR3_IRQ_UP | CPR3_IRQ_DOWN)))
++ break;
++ udelay(10);
++ }
++ if (i == CPR3_REGISTER_WRITE_DELAY_US / 10)
++ cpr3_debug(ctrl, "CPR interrupts not cleared after %d us\n",
++ CPR3_REGISTER_WRITE_DELAY_US);
++
++ cpr3_ctrl_loop_enable(ctrl);
++
++ cpr3_debug(ctrl, "CPR interrupt received but no up or down status bit is set\n");
++
++ mutex_unlock(&ctrl->lock);
++ return IRQ_HANDLED;
++ } else if (up && down) {
++ cpr3_debug(ctrl, "both up and down status bits set\n");
++ /* The up flag takes precedence over the down flag. */
++ down = false;
++ }
++
++ if (ctrl->supports_hw_closed_loop)
++ reg_last_measurement
++ = cpr3_read(ctrl, CPR3_REG_LAST_MEASUREMENT);
++ dynamic_floor_volt = cpr3_regulator_get_dynamic_floor_volt(ctrl,
++ reg_last_measurement);
++
++ local_irq_restore(flags);
++ preempt_enable();
++
++ irq_en = aggr->irq_en;
++ last_volt = aggr->last_volt;
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ if (cpr3_thread_busy(&ctrl->thread[i])) {
++ cpr3_debug(ctrl, "CPR thread %u busy when it should be waiting for SW cont\n",
++ ctrl->thread[i].thread_id);
++ goto done;
++ }
++ }
++
++ new_volt = up ? last_volt + ctrl->step_volt
++ : last_volt - ctrl->step_volt;
++
++ /* Re-enable UP/DOWN interrupt when its opposite is received. */
++ irq_en |= up ? CPR3_IRQ_DOWN : CPR3_IRQ_UP;
++
++ if (new_volt > aggr->ceiling_volt) {
++ new_volt = aggr->ceiling_volt;
++ irq_en &= ~CPR3_IRQ_UP;
++ cpr3_debug(ctrl, "limiting to ceiling=%d uV\n",
++ aggr->ceiling_volt);
++ } else if (new_volt < aggr->floor_volt) {
++ new_volt = aggr->floor_volt;
++ irq_en &= ~CPR3_IRQ_DOWN;
++ cpr3_debug(ctrl, "limiting to floor=%d uV\n", aggr->floor_volt);
++ }
++
++ if (down && new_volt < dynamic_floor_volt) {
++ /*
++ * The vdd-supply voltage should not be decreased below the
++ * dynamic floor voltage. However, it is not necessary (and
++ * counter productive) to force the voltage up to this level
++ * if it happened to be below it since the closed-loop voltage
++ * must have gotten there in a safe manner while the power
++ * domains for the CPR3 regulator imposing the dynamic floor
++ * were not bypassed.
++ */
++ new_volt = last_volt;
++ irq_en &= ~CPR3_IRQ_DOWN;
++ cpr3_debug(ctrl, "limiting to dynamic floor=%d uV\n",
++ dynamic_floor_volt);
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++)
++ cpr3_print_result(&ctrl->thread[i]);
++
++ cpr3_debug(ctrl, "%s: new_volt=%d uV, last_volt=%d uV\n",
++ up ? "UP" : "DN", new_volt, last_volt);
++
++ if (ctrl->proc_clock_throttle && last_volt == aggr->ceiling_volt
++ && new_volt < last_volt)
++ cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
++ ctrl->proc_clock_throttle);
++
++ if (new_volt != last_volt) {
++ rc = cpr3_regulator_scale_vdd_voltage(ctrl, new_volt,
++ last_volt,
++ aggr);
++ if (rc) {
++ cpr3_err(ctrl, "scale_vdd() failed to set vdd=%d uV, rc=%d\n",
++ new_volt, rc);
++ goto done;
++ }
++ cont = CPR3_CONT_CMD_ACK;
++
++ /*
++ * Update the closed-loop voltage for all regulators managed
++ * by this CPR controller.
++ */
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ cpr3_update_vreg_closed_loop_volt(vreg,
++ new_volt, reg_last_measurement);
++ }
++ }
++ }
++
++ if (ctrl->proc_clock_throttle && new_volt == aggr->ceiling_volt)
++ cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
++ CPR3_PD_THROTTLE_DISABLE);
++
++ corner = &ctrl->thread[0].vreg[0].corner[
++ ctrl->thread[0].vreg[0].current_corner];
++
++ if (irq_en != aggr->irq_en) {
++ aggr->irq_en = irq_en;
++ cpr3_write(ctrl, CPR3_REG_IRQ_EN, irq_en);
++ }
++
++ aggr->last_volt = new_volt;
++
++done:
++ /* Clear interrupt status */
++ cpr3_write(ctrl, CPR3_REG_IRQ_CLEAR, CPR3_IRQ_UP | CPR3_IRQ_DOWN);
++
++ /* ACK or NACK the CPR controller */
++ cpr3_write(ctrl, CPR3_REG_CONT_CMD, cont);
++
++ mutex_unlock(&ctrl->lock);
++ return IRQ_HANDLED;
++}
++
++/**
++ * cpr3_ceiling_irq_handler() - CPR ceiling reached interrupt handler callback
++ * function used for hardware closed-loop operation
++ * @irq: CPR ceiling interrupt number
++ * @data: Private data corresponding to the CPR3 controller
++ * pointer
++ *
++ * This function disables processor clock throttling and closed-loop operation
++ * when the ceiling voltage is reached.
++ *
++ * Return: IRQ_HANDLED
++ */
++static irqreturn_t cpr3_ceiling_irq_handler(int irq, void *data)
++{
++ struct cpr3_controller *ctrl = data;
++ int volt;
++
++ mutex_lock(&ctrl->lock);
++
++ if (!ctrl->cpr_enabled) {
++ cpr3_debug(ctrl, "CPR ceiling interrupt received but CPR is disabled\n");
++ goto done;
++ } else if (!ctrl->use_hw_closed_loop) {
++ cpr3_debug(ctrl, "CPR ceiling interrupt received but CPR is using SW closed-loop\n");
++ goto done;
++ }
++
++ volt = regulator_get_voltage(ctrl->vdd_regulator);
++ if (volt < 0) {
++ cpr3_err(ctrl, "could not get vdd voltage, rc=%d\n", volt);
++ goto done;
++ } else if (volt != ctrl->aggr_corner.ceiling_volt) {
++ cpr3_debug(ctrl, "CPR ceiling interrupt received but vdd voltage: %d uV != ceiling voltage: %d uV\n",
++ volt, ctrl->aggr_corner.ceiling_volt);
++ goto done;
++ }
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ /*
++ * Since the ceiling voltage has been reached, disable processor
++ * clock throttling as well as CPR closed-loop operation.
++ */
++ cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
++ CPR3_PD_THROTTLE_DISABLE);
++ cpr3_ctrl_loop_disable(ctrl);
++ cpr3_debug(ctrl, "CPR closed-loop and throttling disabled\n");
++ }
++
++done:
++ mutex_unlock(&ctrl->lock);
++ return IRQ_HANDLED;
++}
++
++/**
++ * cpr3_regulator_vreg_register() - register a regulator device for a CPR3
++ * regulator
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function initializes all regulator framework related structures and then
++ * calls regulator_register() for the CPR3 regulator.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_vreg_register(struct cpr3_regulator *vreg)
++{
++ struct regulator_config config = {};
++ struct regulator_desc *rdesc;
++ struct regulator_init_data *init_data;
++ int rc;
++
++ init_data = of_get_regulator_init_data(vreg->thread->ctrl->dev,
++ vreg->of_node, &vreg->rdesc);
++ if (!init_data) {
++ cpr3_err(vreg, "regulator init data is missing\n");
++ return -EINVAL;
++ }
++
++ init_data->constraints.input_uV = init_data->constraints.max_uV;
++ rdesc = &vreg->rdesc;
++ init_data->constraints.valid_ops_mask |=
++ REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
++ rdesc->ops = &cpr3_regulator_ops;
++
++ rdesc->n_voltages = vreg->corner_count;
++ rdesc->name = init_data->constraints.name;
++ rdesc->owner = THIS_MODULE;
++ rdesc->type = REGULATOR_VOLTAGE;
++
++ config.dev = vreg->thread->ctrl->dev;
++ config.driver_data = vreg;
++ config.init_data = init_data;
++ config.of_node = vreg->of_node;
++
++ vreg->rdev = regulator_register(vreg->thread->ctrl->dev, rdesc, &config);
++ if (IS_ERR(vreg->rdev)) {
++ rc = PTR_ERR(vreg->rdev);
++ cpr3_err(vreg, "regulator_register failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ return 0;
++}
++
++static int debugfs_int_set(void *data, u64 val)
++{
++ *(int *)data = val;
++ return 0;
++}
++
++static int debugfs_int_get(void *data, u64 *val)
++{
++ *val = *(int *)data;
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(fops_int, debugfs_int_get, debugfs_int_set, "%lld\n");
++DEFINE_SIMPLE_ATTRIBUTE(fops_int_ro, debugfs_int_get, NULL, "%lld\n");
++DEFINE_SIMPLE_ATTRIBUTE(fops_int_wo, NULL, debugfs_int_set, "%lld\n");
++
++/**
++ * debugfs_create_int - create a debugfs file that is used to read and write a
++ * signed int value
++ * @name: Pointer to a string containing the name of the file to
++ * create
++ * @mode: The permissions that the file should have
++ * @parent: Pointer to the parent dentry for this file. This should
++ * be a directory dentry if set. If this parameter is
++ * %NULL, then the file will be created in the root of the
++ * debugfs filesystem.
++ * @value: Pointer to the variable that the file should read to and
++ * write from
++ *
++ * This function creates a file in debugfs with the given name that
++ * contains the value of the variable @value. If the @mode variable is so
++ * set, it can be read from, and written to.
++ *
++ * This function will return a pointer to a dentry if it succeeds. This
++ * pointer must be passed to the debugfs_remove() function when the file is
++ * to be removed. If an error occurs, %NULL will be returned.
++ */
++static struct dentry *debugfs_create_int(const char *name, umode_t mode,
++ struct dentry *parent, int *value)
++{
++ /* if there are no write bits set, make read only */
++ if (!(mode & S_IWUGO))
++ return debugfs_create_file(name, mode, parent, value,
++ &fops_int_ro);
++ /* if there are no read bits set, make write only */
++ if (!(mode & S_IRUGO))
++ return debugfs_create_file(name, mode, parent, value,
++ &fops_int_wo);
++
++ return debugfs_create_file(name, mode, parent, value, &fops_int);
++}
++
++static int debugfs_bool_get(void *data, u64 *val)
++{
++ *val = *(bool *)data;
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(fops_bool_ro, debugfs_bool_get, NULL, "%lld\n");
++
++/**
++ * struct cpr3_debug_corner_info - data structure used by the
++ * cpr3_debugfs_create_corner_int function
++ * @vreg: Pointer to the CPR3 regulator
++ * @index: Pointer to the corner array index
++ * @member_offset: Offset in bytes from the beginning of struct cpr3_corner
++ * to the beginning of the value to be read from
++ * @corner: Pointer to the CPR3 corner array
++ */
++struct cpr3_debug_corner_info {
++ struct cpr3_regulator *vreg;
++ int *index;
++ size_t member_offset;
++ struct cpr3_corner *corner;
++};
++
++static int cpr3_debug_corner_int_get(void *data, u64 *val)
++{
++ struct cpr3_debug_corner_info *info = data;
++ struct cpr3_controller *ctrl = info->vreg->thread->ctrl;
++ int i;
++
++ mutex_lock(&ctrl->lock);
++
++ i = *info->index;
++ if (i < 0)
++ i = 0;
++
++ *val = *(int *)((char *)&info->vreg->corner[i] + info->member_offset);
++
++ mutex_unlock(&ctrl->lock);
++
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_corner_int_fops, cpr3_debug_corner_int_get,
++ NULL, "%lld\n");
++
++/**
++ * cpr3_debugfs_create_corner_int - create a debugfs file that is used to read
++ * a signed int value out of a CPR3 regulator's corner array
++ * @vreg: Pointer to the CPR3 regulator
++ * @name: Pointer to a string containing the name of the file to
++ * create
++ * @mode: The permissions that the file should have
++ * @parent: Pointer to the parent dentry for this file. This should
++ * be a directory dentry if set. If this parameter is
++ * %NULL, then the file will be created in the root of the
++ * debugfs filesystem.
++ * @index: Pointer to the corner array index
++ * @member_offset: Offset in bytes from the beginning of struct cpr3_corner
++ * to the beginning of the value to be read from
++ *
++ * This function creates a file in debugfs with the given name that
++ * contains the value of the int type variable vreg->corner[index].member
++ * where member_offset == offsetof(struct cpr3_corner, member).
++ */
++static struct dentry *cpr3_debugfs_create_corner_int(
++ struct cpr3_regulator *vreg, const char *name, umode_t mode,
++ struct dentry *parent, int *index, size_t member_offset)
++{
++ struct cpr3_debug_corner_info *info;
++
++ info = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
++ if (!info)
++ return NULL;
++
++ info->vreg = vreg;
++ info->index = index;
++ info->member_offset = member_offset;
++
++ return debugfs_create_file(name, mode, parent, info,
++ &cpr3_debug_corner_int_fops);
++}
++
++static int cpr3_debug_quot_open(struct inode *inode, struct file *file)
++{
++ struct cpr3_debug_corner_info *info = inode->i_private;
++ struct cpr3_thread *thread = info->vreg->thread;
++ int size, i, pos;
++ u32 *quot;
++ char *buf;
++
++ /*
++ * Max size:
++ * - 10 digits + ' ' or '\n' = 11 bytes per number
++ * - terminating '\0'
++ */
++ size = CPR3_RO_COUNT * 11;
++ buf = kzalloc(size + 1, GFP_KERNEL);
++ if (!buf)
++ return -ENOMEM;
++
++ file->private_data = buf;
++
++ mutex_lock(&thread->ctrl->lock);
++
++ quot = info->corner[*info->index].target_quot;
++
++ for (i = 0, pos = 0; i < CPR3_RO_COUNT; i++)
++ pos += scnprintf(buf + pos, size - pos, "%u%c",
++ quot[i], i < CPR3_RO_COUNT - 1 ? ' ' : '\n');
++
++ mutex_unlock(&thread->ctrl->lock);
++
++ return nonseekable_open(inode, file);
++}
++
++static ssize_t cpr3_debug_quot_read(struct file *file, char __user *buf,
++ size_t len, loff_t *ppos)
++{
++ return simple_read_from_buffer(buf, len, ppos, file->private_data,
++ strlen(file->private_data));
++}
++
++static int cpr3_debug_quot_release(struct inode *inode, struct file *file)
++{
++ kfree(file->private_data);
++
++ return 0;
++}
++
++static const struct file_operations cpr3_debug_quot_fops = {
++ .owner = THIS_MODULE,
++ .open = cpr3_debug_quot_open,
++ .release = cpr3_debug_quot_release,
++ .read = cpr3_debug_quot_read,
++};
++
++/**
++ * cpr3_regulator_debugfs_corner_add() - add debugfs files to expose
++ * configuration data for the CPR corner
++ * @vreg: Pointer to the CPR3 regulator
++ * @corner_dir: Pointer to the parent corner dentry for the new files
++ * @index: Pointer to the corner array index
++ *
++ * Return: none
++ */
++static void cpr3_regulator_debugfs_corner_add(struct cpr3_regulator *vreg,
++ struct dentry *corner_dir, int *index)
++{
++ struct cpr3_debug_corner_info *info;
++ struct dentry *temp;
++
++ temp = cpr3_debugfs_create_corner_int(vreg, "floor_volt", S_IRUGO,
++ corner_dir, index, offsetof(struct cpr3_corner, floor_volt));
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "floor_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = cpr3_debugfs_create_corner_int(vreg, "ceiling_volt", S_IRUGO,
++ corner_dir, index, offsetof(struct cpr3_corner, ceiling_volt));
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "ceiling_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = cpr3_debugfs_create_corner_int(vreg, "open_loop_volt", S_IRUGO,
++ corner_dir, index,
++ offsetof(struct cpr3_corner, open_loop_volt));
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "open_loop_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = cpr3_debugfs_create_corner_int(vreg, "last_volt", S_IRUGO,
++ corner_dir, index, offsetof(struct cpr3_corner, last_volt));
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "last_volt debugfs file creation failed\n");
++ return;
++ }
++
++ info = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
++ if (!info)
++ return;
++
++ info->vreg = vreg;
++ info->index = index;
++ info->corner = vreg->corner;
++
++ temp = debugfs_create_file("target_quots", S_IRUGO, corner_dir,
++ info, &cpr3_debug_quot_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "target_quots debugfs file creation failed\n");
++ return;
++ }
++}
++
++/**
++ * cpr3_debug_corner_index_set() - debugfs callback used to change the
++ * value of the CPR3 regulator debug_corner index
++ * @data: Pointer to private data which is equal to the CPR3
++ * regulator pointer
++ * @val: New value for debug_corner
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_corner_index_set(void *data, u64 val)
++{
++ struct cpr3_regulator *vreg = data;
++
++ if (val < CPR3_CORNER_OFFSET || val > vreg->corner_count) {
++ cpr3_err(vreg, "invalid corner index %llu; allowed values: %d-%d\n",
++ val, CPR3_CORNER_OFFSET, vreg->corner_count);
++ return -EINVAL;
++ }
++
++ mutex_lock(&vreg->thread->ctrl->lock);
++ vreg->debug_corner = val - CPR3_CORNER_OFFSET;
++ mutex_unlock(&vreg->thread->ctrl->lock);
++
++ return 0;
++}
++
++/**
++ * cpr3_debug_corner_index_get() - debugfs callback used to retrieve
++ * the value of the CPR3 regulator debug_corner index
++ * @data: Pointer to private data which is equal to the CPR3
++ * regulator pointer
++ * @val: Output parameter written with the value of
++ * debug_corner
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_corner_index_get(void *data, u64 *val)
++{
++ struct cpr3_regulator *vreg = data;
++
++ *val = vreg->debug_corner + CPR3_CORNER_OFFSET;
++
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_corner_index_fops,
++ cpr3_debug_corner_index_get,
++ cpr3_debug_corner_index_set,
++ "%llu\n");
++
++/**
++ * cpr3_debug_current_corner_index_get() - debugfs callback used to retrieve
++ * the value of the CPR3 regulator current_corner index
++ * @data: Pointer to private data which is equal to the CPR3
++ * regulator pointer
++ * @val: Output parameter written with the value of
++ * current_corner
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_current_corner_index_get(void *data, u64 *val)
++{
++ struct cpr3_regulator *vreg = data;
++
++ *val = vreg->current_corner + CPR3_CORNER_OFFSET;
++
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_current_corner_index_fops,
++ cpr3_debug_current_corner_index_get,
++ NULL, "%llu\n");
++
++/**
++ * cpr3_regulator_debugfs_vreg_add() - add debugfs files to expose configuration
++ * data for the CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ * @thread_dir CPR3 thread debugfs directory handle
++ *
++ * Return: none
++ */
++static void cpr3_regulator_debugfs_vreg_add(struct cpr3_regulator *vreg,
++ struct dentry *thread_dir)
++{
++ struct dentry *temp, *corner_dir, *vreg_dir;
++
++ vreg_dir = debugfs_create_dir(vreg->name, thread_dir);
++ if (IS_ERR_OR_NULL(vreg_dir)) {
++ cpr3_err(vreg, "%s debugfs directory creation failed\n",
++ vreg->name);
++ return;
++ }
++
++ temp = debugfs_create_int("speed_bin_fuse", S_IRUGO, vreg_dir,
++ &vreg->speed_bin_fuse);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "speed_bin_fuse debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("cpr_rev_fuse", S_IRUGO, vreg_dir,
++ &vreg->cpr_rev_fuse);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "cpr_rev_fuse debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("fuse_combo", S_IRUGO, vreg_dir,
++ &vreg->fuse_combo);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "fuse_combo debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("corner_count", S_IRUGO, vreg_dir,
++ &vreg->corner_count);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "corner_count debugfs file creation failed\n");
++ return;
++ }
++
++ corner_dir = debugfs_create_dir("corner", vreg_dir);
++ if (IS_ERR_OR_NULL(corner_dir)) {
++ cpr3_err(vreg, "corner debugfs directory creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_file("index", S_IRUGO | S_IWUSR, corner_dir,
++ vreg, &cpr3_debug_corner_index_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "index debugfs file creation failed\n");
++ return;
++ }
++
++ cpr3_regulator_debugfs_corner_add(vreg, corner_dir,
++ &vreg->debug_corner);
++
++ corner_dir = debugfs_create_dir("current_corner", vreg_dir);
++ if (IS_ERR_OR_NULL(corner_dir)) {
++ cpr3_err(vreg, "current_corner debugfs directory creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_file("index", S_IRUGO, corner_dir,
++ vreg, &cpr3_debug_current_corner_index_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(vreg, "index debugfs file creation failed\n");
++ return;
++ }
++
++ cpr3_regulator_debugfs_corner_add(vreg, corner_dir,
++ &vreg->current_corner);
++}
++
++/**
++ * cpr3_regulator_debugfs_thread_add() - add debugfs files to expose
++ * configuration data for the CPR thread
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: none
++ */
++static void cpr3_regulator_debugfs_thread_add(struct cpr3_thread *thread)
++{
++ struct cpr3_controller *ctrl = thread->ctrl;
++ struct dentry *aggr_dir, *temp, *thread_dir;
++ struct cpr3_debug_corner_info *info;
++ char buf[20];
++ int *index;
++ int i;
++
++ scnprintf(buf, sizeof(buf), "thread%u", thread->thread_id);
++ thread_dir = debugfs_create_dir(buf, thread->ctrl->debugfs);
++ if (IS_ERR_OR_NULL(thread_dir)) {
++ cpr3_err(ctrl, "thread %u %s debugfs directory creation failed\n",
++ thread->thread_id, buf);
++ return;
++ }
++
++ aggr_dir = debugfs_create_dir("max_aggregated_params", thread_dir);
++ if (IS_ERR_OR_NULL(aggr_dir)) {
++ cpr3_err(ctrl, "thread %u max_aggregated_params debugfs directory creation failed\n",
++ thread->thread_id);
++ return;
++ }
++
++ temp = debugfs_create_int("floor_volt", S_IRUGO, aggr_dir,
++ &thread->aggr_corner.floor_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "thread %u aggr floor_volt debugfs file creation failed\n",
++ thread->thread_id);
++ return;
++ }
++
++ temp = debugfs_create_int("ceiling_volt", S_IRUGO, aggr_dir,
++ &thread->aggr_corner.ceiling_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "thread %u aggr ceiling_volt debugfs file creation failed\n",
++ thread->thread_id);
++ return;
++ }
++
++ temp = debugfs_create_int("open_loop_volt", S_IRUGO, aggr_dir,
++ &thread->aggr_corner.open_loop_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "thread %u aggr open_loop_volt debugfs file creation failed\n",
++ thread->thread_id);
++ return;
++ }
++
++ temp = debugfs_create_int("last_volt", S_IRUGO, aggr_dir,
++ &thread->aggr_corner.last_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "thread %u aggr last_volt debugfs file creation failed\n",
++ thread->thread_id);
++ return;
++ }
++
++ info = devm_kzalloc(thread->ctrl->dev, sizeof(*info), GFP_KERNEL);
++ index = devm_kzalloc(thread->ctrl->dev, sizeof(*index), GFP_KERNEL);
++ if (!info || !index)
++ return;
++ *index = 0;
++ info->vreg = &thread->vreg[0];
++ info->index = index;
++ info->corner = &thread->aggr_corner;
++
++ temp = debugfs_create_file("target_quots", S_IRUGO, aggr_dir,
++ info, &cpr3_debug_quot_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "thread %u target_quots debugfs file creation failed\n",
++ thread->thread_id);
++ return;
++ }
++
++ for (i = 0; i < thread->vreg_count; i++)
++ cpr3_regulator_debugfs_vreg_add(&thread->vreg[i], thread_dir);
++}
++
++/**
++ * cpr3_debug_closed_loop_enable_set() - debugfs callback used to change the
++ * value of the CPR controller cpr_allowed_sw flag which enables or
++ * disables closed-loop operation
++ * @data: Pointer to private data which is equal to the CPR
++ * controller pointer
++ * @val: New value for cpr_allowed_sw
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_closed_loop_enable_set(void *data, u64 val)
++{
++ struct cpr3_controller *ctrl = data;
++ bool enable = !!val;
++ int rc;
++
++ mutex_lock(&ctrl->lock);
++
++ if (ctrl->cpr_allowed_sw == enable)
++ goto done;
++
++ if (enable && !ctrl->cpr_allowed_hw) {
++ cpr3_err(ctrl, "CPR closed-loop operation is not allowed\n");
++ goto done;
++ }
++
++ ctrl->cpr_allowed_sw = enable;
++
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not change CPR enable state=%u, rc=%d\n",
++ enable, rc);
++ goto done;
++ }
++
++ if (ctrl->proc_clock_throttle && !ctrl->cpr_enabled) {
++ rc = cpr3_clock_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "clock enable failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ ctrl->cpr_enabled = true;
++
++ cpr3_write(ctrl, CPR3_REG_PD_THROTTLE,
++ CPR3_PD_THROTTLE_DISABLE);
++
++ cpr3_clock_disable(ctrl);
++ ctrl->cpr_enabled = false;
++ }
++
++ cpr3_debug(ctrl, "closed-loop=%s\n", enable ? "enabled" : "disabled");
++done:
++ mutex_unlock(&ctrl->lock);
++ return 0;
++}
++
++/**
++ * cpr3_debug_closed_loop_enable_get() - debugfs callback used to retrieve
++ * the value of the CPR controller cpr_allowed_sw flag which
++ * indicates if closed-loop operation is enabled
++ * @data: Pointer to private data which is equal to the CPR
++ * controller pointer
++ * @val: Output parameter written with the value of
++ * cpr_allowed_sw
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_closed_loop_enable_get(void *data, u64 *val)
++{
++ struct cpr3_controller *ctrl = data;
++
++ *val = ctrl->cpr_allowed_sw;
++
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_closed_loop_enable_fops,
++ cpr3_debug_closed_loop_enable_get,
++ cpr3_debug_closed_loop_enable_set,
++ "%llu\n");
++
++/**
++ * cpr3_debug_hw_closed_loop_enable_set() - debugfs callback used to change the
++ * value of the CPR controller use_hw_closed_loop flag which
++ * switches between software closed-loop and hardware closed-loop
++ * operation for CPR3 and CPR4 controllers and between open-loop
++ * and full hardware closed-loop operation for CPRh controllers.
++ * @data: Pointer to private data which is equal to the CPR
++ * controller pointer
++ * @val: New value for use_hw_closed_loop
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_hw_closed_loop_enable_set(void *data, u64 val)
++{
++ struct cpr3_controller *ctrl = data;
++ bool use_hw_closed_loop = !!val;
++ struct cpr3_regulator *vreg;
++ bool cpr_enabled;
++ int i, j, k, rc;
++
++ mutex_lock(&ctrl->lock);
++
++ if (ctrl->use_hw_closed_loop == use_hw_closed_loop)
++ goto done;
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ ctrl->use_hw_closed_loop = use_hw_closed_loop;
++
++ cpr_enabled = ctrl->cpr_enabled;
++
++ /* Ensure that CPR clocks are enabled before writing to registers. */
++ if (!cpr_enabled) {
++ rc = cpr3_clock_enable(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
++ goto done;
++ }
++ ctrl->cpr_enabled = true;
++ }
++
++ if (ctrl->use_hw_closed_loop)
++ cpr3_write(ctrl, CPR3_REG_IRQ_EN, 0);
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
++ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
++ ctrl->use_hw_closed_loop
++ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
++ : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
++ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ cpr3_write(ctrl, CPR3_REG_HW_CLOSED_LOOP,
++ ctrl->use_hw_closed_loop
++ ? CPR3_HW_CLOSED_LOOP_ENABLE
++ : CPR3_HW_CLOSED_LOOP_DISABLE);
++ }
++
++ /* Turn off CPR clocks if they were off before this function call. */
++ if (!cpr_enabled) {
++ cpr3_clock_disable(ctrl);
++ ctrl->cpr_enabled = false;
++ }
++
++ if (ctrl->use_hw_closed_loop && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ rc = regulator_enable(ctrl->vdd_limit_regulator);
++ if (rc) {
++ cpr3_err(ctrl, "CPR limit regulator enable failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ } else if (!ctrl->use_hw_closed_loop
++ && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ rc = regulator_disable(ctrl->vdd_limit_regulator);
++ if (rc) {
++ cpr3_err(ctrl, "CPR limit regulator disable failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ /*
++ * Due to APM and mem-acc floor restriction constraints,
++ * the closed-loop voltage may be different when using
++ * software closed-loop vs hardware closed-loop. Therefore,
++ * reset the cached closed-loop voltage for all corners to the
++ * corresponding open-loop voltage when switching between
++ * SW and HW closed-loop mode.
++ */
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ for (k = 0; k < vreg->corner_count; k++)
++ vreg->corner[k].last_volt
++ = vreg->corner[k].open_loop_volt;
++ }
++ }
++
++ /* Skip last_volt caching */
++ ctrl->last_corner_was_closed_loop = false;
++
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not change CPR HW closed-loop enable state=%u, rc=%d\n",
++ use_hw_closed_loop, rc);
++ goto done;
++ }
++
++ cpr3_debug(ctrl, "CPR mode=%s\n",
++ use_hw_closed_loop ?
++ "HW closed-loop" : "SW closed-loop");
++done:
++ mutex_unlock(&ctrl->lock);
++ return 0;
++}
++
++/**
++ * cpr3_debug_hw_closed_loop_enable_get() - debugfs callback used to retrieve
++ * the value of the CPR controller use_hw_closed_loop flag which
++ * indicates if hardware closed-loop operation is being used in
++ * place of software closed-loop operation
++ * @data: Pointer to private data which is equal to the CPR
++ * controller pointer
++ * @val: Output parameter written with the value of
++ * use_hw_closed_loop
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_hw_closed_loop_enable_get(void *data, u64 *val)
++{
++ struct cpr3_controller *ctrl = data;
++
++ *val = ctrl->use_hw_closed_loop;
++
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_hw_closed_loop_enable_fops,
++ cpr3_debug_hw_closed_loop_enable_get,
++ cpr3_debug_hw_closed_loop_enable_set,
++ "%llu\n");
++
++/**
++ * cpr3_debug_trigger_aging_measurement_set() - debugfs callback used to trigger
++ * another CPR measurement
++ * @data: Pointer to private data which is equal to the CPR
++ * controller pointer
++ * @val: Unused
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_debug_trigger_aging_measurement_set(void *data, u64 val)
++{
++ struct cpr3_controller *ctrl = data;
++ int rc;
++
++ mutex_lock(&ctrl->lock);
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ cpr3_regulator_set_aging_ref_adjustment(ctrl, INT_MAX);
++ ctrl->aging_required = true;
++ ctrl->aging_succeeded = false;
++ ctrl->aging_failed = false;
++
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not update the CPR controller state, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++done:
++ mutex_unlock(&ctrl->lock);
++ return 0;
++}
++DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_trigger_aging_measurement_fops,
++ NULL,
++ cpr3_debug_trigger_aging_measurement_set,
++ "%llu\n");
++
++/**
++ * cpr3_regulator_debugfs_ctrl_add() - add debugfs files to expose configuration
++ * data for the CPR controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: none
++ */
++static void cpr3_regulator_debugfs_ctrl_add(struct cpr3_controller *ctrl)
++{
++ struct dentry *temp, *aggr_dir;
++ int i;
++
++ /* Add cpr3-regulator base directory if it isn't present already. */
++ if (cpr3_debugfs_base == NULL) {
++ cpr3_debugfs_base = debugfs_create_dir("cpr3-regulator", NULL);
++ if (IS_ERR_OR_NULL(cpr3_debugfs_base)) {
++ cpr3_err(ctrl, "cpr3-regulator debugfs base directory creation failed\n");
++ cpr3_debugfs_base = NULL;
++ return;
++ }
++ }
++
++ ctrl->debugfs = debugfs_create_dir(ctrl->name, cpr3_debugfs_base);
++ if (IS_ERR_OR_NULL(ctrl->debugfs)) {
++ cpr3_err(ctrl, "cpr3-regulator controller debugfs directory creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_file("cpr_closed_loop_enable", S_IRUGO | S_IWUSR,
++ ctrl->debugfs, ctrl,
++ &cpr3_debug_closed_loop_enable_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "cpr_closed_loop_enable debugfs file creation failed\n");
++ return;
++ }
++
++ if (ctrl->supports_hw_closed_loop) {
++ temp = debugfs_create_file("use_hw_closed_loop",
++ S_IRUGO | S_IWUSR, ctrl->debugfs, ctrl,
++ &cpr3_debug_hw_closed_loop_enable_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "use_hw_closed_loop debugfs file creation failed\n");
++ return;
++ }
++ }
++
++ temp = debugfs_create_int("thread_count", S_IRUGO, ctrl->debugfs,
++ &ctrl->thread_count);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "thread_count debugfs file creation failed\n");
++ return;
++ }
++
++ if (ctrl->apm) {
++ temp = debugfs_create_int("apm_threshold_volt", S_IRUGO,
++ ctrl->debugfs, &ctrl->apm_threshold_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "apm_threshold_volt debugfs file creation failed\n");
++ return;
++ }
++ }
++
++ if (ctrl->aging_required || ctrl->aging_succeeded
++ || ctrl->aging_failed) {
++ temp = debugfs_create_int("aging_adj_volt", S_IRUGO,
++ ctrl->debugfs, &ctrl->aging_ref_adjust_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aging_adj_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_file("aging_succeeded", S_IRUGO,
++ ctrl->debugfs, &ctrl->aging_succeeded, &fops_bool_ro);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aging_succeeded debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_file("aging_failed", S_IRUGO,
++ ctrl->debugfs, &ctrl->aging_failed, &fops_bool_ro);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aging_failed debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_file("aging_trigger", S_IWUSR,
++ ctrl->debugfs, ctrl,
++ &cpr3_debug_trigger_aging_measurement_fops);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aging_trigger debugfs file creation failed\n");
++ return;
++ }
++ }
++
++ aggr_dir = debugfs_create_dir("max_aggregated_voltages", ctrl->debugfs);
++ if (IS_ERR_OR_NULL(aggr_dir)) {
++ cpr3_err(ctrl, "max_aggregated_voltages debugfs directory creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("floor_volt", S_IRUGO, aggr_dir,
++ &ctrl->aggr_corner.floor_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aggr floor_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("ceiling_volt", S_IRUGO, aggr_dir,
++ &ctrl->aggr_corner.ceiling_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aggr ceiling_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("open_loop_volt", S_IRUGO, aggr_dir,
++ &ctrl->aggr_corner.open_loop_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aggr open_loop_volt debugfs file creation failed\n");
++ return;
++ }
++
++ temp = debugfs_create_int("last_volt", S_IRUGO, aggr_dir,
++ &ctrl->aggr_corner.last_volt);
++ if (IS_ERR_OR_NULL(temp)) {
++ cpr3_err(ctrl, "aggr last_volt debugfs file creation failed\n");
++ return;
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++)
++ cpr3_regulator_debugfs_thread_add(&ctrl->thread[i]);
++}
++
++/**
++ * cpr3_regulator_debugfs_ctrl_remove() - remove debugfs files for the CPR
++ * controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Note, this function must be called after the controller has been removed from
++ * cpr3_controller_list and while the cpr3_controller_list_mutex lock is held.
++ *
++ * Return: none
++ */
++static void cpr3_regulator_debugfs_ctrl_remove(struct cpr3_controller *ctrl)
++{
++ if (list_empty(&cpr3_controller_list)) {
++ debugfs_remove_recursive(cpr3_debugfs_base);
++ cpr3_debugfs_base = NULL;
++ } else {
++ debugfs_remove_recursive(ctrl->debugfs);
++ }
++}
++
++/**
++ * cpr3_regulator_init_ctrl_data() - performs initialization of CPR controller
++ * elements
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_init_ctrl_data(struct cpr3_controller *ctrl)
++{
++ /* Read the initial vdd voltage from hardware. */
++ ctrl->aggr_corner.last_volt
++ = regulator_get_voltage(ctrl->vdd_regulator);
++ if (ctrl->aggr_corner.last_volt < 0) {
++ cpr3_err(ctrl, "regulator_get_voltage(vdd) failed, rc=%d\n",
++ ctrl->aggr_corner.last_volt);
++ return ctrl->aggr_corner.last_volt;
++ }
++ ctrl->aggr_corner.open_loop_volt = ctrl->aggr_corner.last_volt;
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_init_vreg_data() - performs initialization of common CPR3
++ * regulator elements and validate aging configurations
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_init_vreg_data(struct cpr3_regulator *vreg)
++{
++ int i, j;
++ bool init_aging;
++
++ vreg->current_corner = CPR3_REGULATOR_CORNER_INVALID;
++ vreg->last_closed_loop_corner = CPR3_REGULATOR_CORNER_INVALID;
++
++ init_aging = vreg->aging_allowed && vreg->thread->ctrl->aging_required;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ vreg->corner[i].last_volt = vreg->corner[i].open_loop_volt;
++ vreg->corner[i].irq_en = CPR3_IRQ_UP | CPR3_IRQ_DOWN;
++
++ vreg->corner[i].ro_mask = 0;
++ for (j = 0; j < CPR3_RO_COUNT; j++) {
++ if (vreg->corner[i].target_quot[j] == 0)
++ vreg->corner[i].ro_mask |= BIT(j);
++ }
++
++ if (init_aging) {
++ vreg->corner[i].unaged_floor_volt
++ = vreg->corner[i].floor_volt;
++ vreg->corner[i].unaged_ceiling_volt
++ = vreg->corner[i].ceiling_volt;
++ vreg->corner[i].unaged_open_loop_volt
++ = vreg->corner[i].open_loop_volt;
++ }
++
++ if (vreg->aging_allowed) {
++ if (vreg->corner[i].unaged_floor_volt <= 0) {
++ cpr3_err(vreg, "invalid unaged_floor_volt[%d] = %d\n",
++ i, vreg->corner[i].unaged_floor_volt);
++ return -EINVAL;
++ }
++ if (vreg->corner[i].unaged_ceiling_volt <= 0) {
++ cpr3_err(vreg, "invalid unaged_ceiling_volt[%d] = %d\n",
++ i, vreg->corner[i].unaged_ceiling_volt);
++ return -EINVAL;
++ }
++ if (vreg->corner[i].unaged_open_loop_volt <= 0) {
++ cpr3_err(vreg, "invalid unaged_open_loop_volt[%d] = %d\n",
++ i, vreg->corner[i].unaged_open_loop_volt);
++ return -EINVAL;
++ }
++ }
++ }
++
++ if (vreg->aging_allowed && vreg->corner[vreg->aging_corner].ceiling_volt
++ > vreg->thread->ctrl->aging_ref_volt) {
++ cpr3_err(vreg, "aging corner %d ceiling voltage = %d > aging ref voltage = %d uV\n",
++ vreg->aging_corner,
++ vreg->corner[vreg->aging_corner].ceiling_volt,
++ vreg->thread->ctrl->aging_ref_volt);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_regulator_suspend() - perform common required CPR3 power down steps
++ * before the system enters suspend
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ mutex_lock(&ctrl->lock);
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc);
++ mutex_unlock(&ctrl->lock);
++ return rc;
++ }
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ rc = cpr3_closed_loop_disable(ctrl);
++ if (rc)
++ cpr3_err(ctrl, "could not disable CPR, rc=%d\n", rc);
++
++ ctrl->cpr_suspended = true;
++
++ mutex_unlock(&ctrl->lock);
++ return 0;
++}
++
++/**
++ * cpr3_regulator_resume() - perform common required CPR3 power up steps after
++ * the system resumes from suspend
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_regulator_resume(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ mutex_lock(&ctrl->lock);
++
++ ctrl->cpr_suspended = false;
++ rc = cpr3_regulator_update_ctrl_state(ctrl);
++ if (rc)
++ cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
++
++ mutex_unlock(&ctrl->lock);
++ return 0;
++}
++
++/**
++ * cpr3_regulator_validate_controller() - verify the data passed in via the
++ * cpr3_controller data structure
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_regulator_validate_controller(struct cpr3_controller *ctrl)
++{
++ struct cpr3_thread *thread;
++ struct cpr3_regulator *vreg;
++ int i, j, allow_boost_vreg_count = 0;
++
++ if (!ctrl->vdd_regulator) {
++ cpr3_err(ctrl, "vdd regulator missing\n");
++ return -EINVAL;
++ } else if (ctrl->sensor_count <= 0
++ || ctrl->sensor_count > CPR3_MAX_SENSOR_COUNT) {
++ cpr3_err(ctrl, "invalid CPR sensor count=%d\n",
++ ctrl->sensor_count);
++ return -EINVAL;
++ } else if (!ctrl->sensor_owner) {
++ cpr3_err(ctrl, "CPR sensor ownership table missing\n");
++ return -EINVAL;
++ }
++
++ if (ctrl->aging_required) {
++ for (i = 0; i < ctrl->aging_sensor_count; i++) {
++ if (ctrl->aging_sensor[i].sensor_id
++ >= ctrl->sensor_count) {
++ cpr3_err(ctrl, "aging_sensor[%d] id=%u is not in the value range 0-%d",
++ i, ctrl->aging_sensor[i].sensor_id,
++ ctrl->sensor_count - 1);
++ return -EINVAL;
++ }
++ }
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ thread = &ctrl->thread[i];
++ for (j = 0; j < thread->vreg_count; j++) {
++ vreg = &thread->vreg[j];
++ if (vreg->allow_boost)
++ allow_boost_vreg_count++;
++ }
++ }
++
++ if (allow_boost_vreg_count > 1) {
++ /*
++ * Boost feature is not allowed to be used for more
++ * than one CPR3 regulator of a CPR3 controller.
++ */
++ cpr3_err(ctrl, "Boost feature is enabled for more than one regulator\n");
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_panic_callback() - panic notification callback function. This function
++ * is invoked when a kernel panic occurs.
++ * @nfb: Notifier block pointer of CPR3 controller
++ * @event: Value passed unmodified to notifier function
++ * @data: Pointer passed unmodified to notifier function
++ *
++ * Return: NOTIFY_OK
++ */
++static int cpr3_panic_callback(struct notifier_block *nfb,
++ unsigned long event, void *data)
++{
++ struct cpr3_controller *ctrl = container_of(nfb,
++ struct cpr3_controller, panic_notifier);
++ struct cpr3_panic_regs_info *regs_info = ctrl->panic_regs_info;
++ struct cpr3_reg_info *reg;
++ int i = 0;
++
++ for (i = 0; i < regs_info->reg_count; i++) {
++ reg = &(regs_info->regs[i]);
++ reg->value = readl_relaxed(reg->virt_addr);
++ pr_err("%s[0x%08x] = 0x%08x\n", reg->name, reg->addr,
++ reg->value);
++ }
++ /*
++ * Barrier to ensure that the information has been updated in the
++ * structure.
++ */
++ mb();
++
++ return NOTIFY_OK;
++}
++
++/**
++ * cpr3_regulator_register() - register the regulators for a CPR3 controller and
++ * perform CPR hardware initialization
++ * @pdev: Platform device pointer for the CPR3 controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_regulator_register(struct platform_device *pdev,
++ struct cpr3_controller *ctrl)
++{
++ struct device *dev = &pdev->dev;
++ struct resource *res;
++ int i, j, rc;
++
++ if (!dev->of_node) {
++ dev_err(dev, "%s: Device tree node is missing\n", __func__);
++ return -EINVAL;
++ }
++
++ if (!ctrl || !ctrl->name) {
++ dev_err(dev, "%s: CPR controller data is missing\n", __func__);
++ return -EINVAL;
++ }
++
++ rc = cpr3_regulator_validate_controller(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "controller validation failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ mutex_init(&ctrl->lock);
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cpr_ctrl");
++ if (!res || !res->start) {
++ cpr3_err(ctrl, "CPR controller address is missing\n");
++ return -ENXIO;
++ }
++ ctrl->cpr_ctrl_base = devm_ioremap(dev, res->start, resource_size(res));
++
++ if (ctrl->aging_possible_mask) {
++ /*
++ * Aging possible register address is required if an aging
++ * possible mask has been specified.
++ */
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
++ "aging_allowed");
++ if (!res || !res->start) {
++ cpr3_err(ctrl, "CPR aging allowed address is missing\n");
++ return -ENXIO;
++ }
++ ctrl->aging_possible_reg = devm_ioremap(dev, res->start,
++ resource_size(res));
++ }
++
++ ctrl->irq = platform_get_irq_byname(pdev, "cpr");
++ if (ctrl->irq < 0) {
++ cpr3_err(ctrl, "missing CPR interrupt\n");
++ return ctrl->irq;
++ }
++
++ if (ctrl->supports_hw_closed_loop) {
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ ctrl->ceiling_irq = platform_get_irq_byname(pdev,
++ "ceiling");
++ if (ctrl->ceiling_irq < 0) {
++ cpr3_err(ctrl, "missing ceiling interrupt\n");
++ return ctrl->ceiling_irq;
++ }
++ }
++ }
++
++ rc = cpr3_regulator_init_ctrl_data(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ rc = cpr3_regulator_init_vreg_data(
++ &ctrl->thread[i].vreg[j]);
++ if (rc)
++ return rc;
++ cpr3_print_quots(&ctrl->thread[i].vreg[j]);
++ }
++ }
++
++ /*
++ * Add the maximum possible aging voltage margin until it is possible
++ * to perform an aging measurement.
++ */
++ if (ctrl->aging_required)
++ cpr3_regulator_set_aging_ref_adjustment(ctrl, INT_MAX);
++
++ rc = cpr3_regulator_init_ctrl(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "CPR controller initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ /* Register regulator devices for all threads. */
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ rc = cpr3_regulator_vreg_register(
++ &ctrl->thread[i].vreg[j]);
++ if (rc) {
++ cpr3_err(&ctrl->thread[i].vreg[j], "failed to register regulator, rc=%d\n",
++ rc);
++ goto free_regulators;
++ }
++ }
++ }
++
++ rc = devm_request_threaded_irq(dev, ctrl->irq, NULL,
++ cpr3_irq_handler,
++ IRQF_ONESHOT |
++ IRQF_TRIGGER_RISING,
++ "cpr3", ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not request IRQ %d, rc=%d\n",
++ ctrl->irq, rc);
++ goto free_regulators;
++ }
++
++ if (ctrl->supports_hw_closed_loop &&
++ ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
++ rc = devm_request_threaded_irq(dev, ctrl->ceiling_irq, NULL,
++ cpr3_ceiling_irq_handler,
++ IRQF_ONESHOT | IRQF_TRIGGER_RISING,
++ "cpr3_ceiling", ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "could not request ceiling IRQ %d, rc=%d\n",
++ ctrl->ceiling_irq, rc);
++ goto free_regulators;
++ }
++ }
++
++ mutex_lock(&cpr3_controller_list_mutex);
++ cpr3_regulator_debugfs_ctrl_add(ctrl);
++ list_add(&ctrl->list, &cpr3_controller_list);
++ mutex_unlock(&cpr3_controller_list_mutex);
++
++ if (ctrl->panic_regs_info) {
++ /* Register panic notification call back */
++ ctrl->panic_notifier.notifier_call = cpr3_panic_callback;
++ atomic_notifier_chain_register(&panic_notifier_list,
++ &ctrl->panic_notifier);
++ }
++
++ return 0;
++
++free_regulators:
++ for (i = 0; i < ctrl->thread_count; i++)
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++)
++ if (!IS_ERR_OR_NULL(ctrl->thread[i].vreg[j].rdev))
++ regulator_unregister(
++ ctrl->thread[i].vreg[j].rdev);
++ return rc;
++}
++
++/**
++ * cpr3_open_loop_regulator_register() - register the regulators for a CPR3
++ * controller which will always work in Open loop and
++ * won't support close loop.
++ * @pdev: Platform device pointer for the CPR3 controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_open_loop_regulator_register(struct platform_device *pdev,
++ struct cpr3_controller *ctrl)
++{
++ struct device *dev = &pdev->dev;
++ struct cpr3_regulator *vreg;
++ int i, j, rc;
++
++ if (!dev->of_node) {
++ dev_err(dev, "%s: Device tree node is missing\n", __func__);
++ return -EINVAL;
++ }
++
++ if (!ctrl || !ctrl->name) {
++ dev_err(dev, "%s: CPR controller data is missing\n", __func__);
++ return -EINVAL;
++ }
++
++ if (!ctrl->vdd_regulator) {
++ cpr3_err(ctrl, "vdd regulator missing\n");
++ return -EINVAL;
++ }
++
++ mutex_init(&ctrl->lock);
++
++ rc = cpr3_regulator_init_ctrl_data(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "CPR controller data initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ vreg = &ctrl->thread[i].vreg[j];
++ vreg->corner[i].last_volt =
++ vreg->corner[i].open_loop_volt;
++ }
++ }
++
++ /* Register regulator devices for all threads. */
++ for (i = 0; i < ctrl->thread_count; i++) {
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
++ rc = cpr3_regulator_vreg_register(
++ &ctrl->thread[i].vreg[j]);
++ if (rc) {
++ cpr3_err(&ctrl->thread[i].vreg[j], "failed to register regulator, rc=%d\n",
++ rc);
++ goto free_regulators;
++ }
++ }
++ }
++
++ mutex_lock(&cpr3_controller_list_mutex);
++ list_add(&ctrl->list, &cpr3_controller_list);
++ mutex_unlock(&cpr3_controller_list_mutex);
++
++ return 0;
++
++free_regulators:
++ for (i = 0; i < ctrl->thread_count; i++)
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++)
++ if (!IS_ERR_OR_NULL(ctrl->thread[i].vreg[j].rdev))
++ regulator_unregister(
++ ctrl->thread[i].vreg[j].rdev);
++ return rc;
++}
++
++/**
++ * cpr3_regulator_unregister() - unregister the regulators for a CPR3 controller
++ * and perform CPR hardware shutdown
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_regulator_unregister(struct cpr3_controller *ctrl)
++{
++ int i, j, rc = 0;
++
++ mutex_lock(&cpr3_controller_list_mutex);
++ list_del(&ctrl->list);
++ cpr3_regulator_debugfs_ctrl_remove(ctrl);
++ mutex_unlock(&cpr3_controller_list_mutex);
++
++ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
++ rc = cpr3_ctrl_clear_cpr4_config(ctrl);
++ if (rc)
++ cpr3_err(ctrl, "failed to clear CPR4 configuration,rc=%d\n",
++ rc);
++ }
++
++ cpr3_ctrl_loop_disable(ctrl);
++
++ cpr3_closed_loop_disable(ctrl);
++
++ if (ctrl->vdd_limit_regulator) {
++ regulator_disable(ctrl->vdd_limit_regulator);
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++)
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++)
++ regulator_unregister(ctrl->thread[i].vreg[j].rdev);
++
++ if (ctrl->panic_notifier.notifier_call)
++ atomic_notifier_chain_unregister(&panic_notifier_list,
++ &ctrl->panic_notifier);
++
++ return 0;
++}
++
++/**
++ * cpr3_open_loop_regulator_unregister() - unregister the regulators for a CPR3
++ * open loop controller and perform CPR hardware shutdown
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl)
++{
++ int i, j;
++
++ mutex_lock(&cpr3_controller_list_mutex);
++ list_del(&ctrl->list);
++ mutex_unlock(&cpr3_controller_list_mutex);
++
++ if (ctrl->vdd_limit_regulator) {
++ regulator_disable(ctrl->vdd_limit_regulator);
++ }
++
++ for (i = 0; i < ctrl->thread_count; i++)
++ for (j = 0; j < ctrl->thread[i].vreg_count; j++)
++ regulator_unregister(ctrl->thread[i].vreg[j].rdev);
++
++ if (ctrl->panic_notifier.notifier_call)
++ atomic_notifier_chain_unregister(&panic_notifier_list,
++ &ctrl->panic_notifier);
++
++ return 0;
++}
+--- /dev/null
++++ b/drivers/regulator/cpr3-regulator.h
+@@ -0,0 +1,1211 @@
++/*
++ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#ifndef __REGULATOR_CPR3_REGULATOR_H__
++#define __REGULATOR_CPR3_REGULATOR_H__
++
++#include <linux/clk.h>
++#include <linux/mutex.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/types.h>
++#include <linux/power/qcom/apm.h>
++#include <linux/regulator/driver.h>
++
++struct cpr3_controller;
++struct cpr3_thread;
++
++/**
++ * struct cpr3_fuse_param - defines one contiguous segment of a fuse parameter
++ * that is contained within a given row.
++ * @row: Fuse row number
++ * @bit_start: The first bit within the row of the fuse parameter segment
++ * @bit_end: The last bit within the row of the fuse parameter segment
++ *
++ * Each fuse row is 64 bits in length. bit_start and bit_end may take values
++ * from 0 to 63. bit_start must be less than or equal to bit_end.
++ */
++struct cpr3_fuse_param {
++ unsigned row;
++ unsigned bit_start;
++ unsigned bit_end;
++};
++
++/* Each CPR3 sensor has 16 ring oscillators */
++#define CPR3_RO_COUNT 16
++
++/* The maximum number of sensors that can be present on a single CPR loop. */
++#define CPR3_MAX_SENSOR_COUNT 256
++
++/* This constant is used when allocating array printing buffers. */
++#define MAX_CHARS_PER_INT 10
++
++/**
++ * struct cpr4_sdelta - CPR4 controller specific data structure for the sdelta
++ * adjustment table which is used to adjust the VDD supply
++ * voltage automatically based upon the temperature and/or
++ * the number of online CPU cores.
++ * @allow_core_count_adj: Core count adjustments are allowed.
++ * @allow_temp_adj: Temperature based adjustments are allowed.
++ * @max_core_count: Maximum number of cores considered for core count
++ * adjustment logic.
++ * @temp_band_count: Number of temperature bands considered for temperature
++ * based adjustment logic.
++ * @cap_volt: CAP in uV to apply to SDELTA margins with multiple
++ * cpr3-regulators defined for single controller.
++ * @table: SDELTA table with per-online-core and temperature based
++ * adjustments of size (max_core_count * temp_band_count)
++ * Outer: core count
++ * Inner: temperature band
++ * Each element has units of VDD supply steps. Positive
++ * values correspond to a reduction in voltage and negative
++ * value correspond to an increase (this follows the SDELTA
++ * register semantics).
++ * @allow_boost: Voltage boost allowed.
++ * @boost_num_cores: The number of online cores at which the boost voltage
++ * adjustments will be applied
++ * @boost_table: SDELTA table with boost voltage adjustments of size
++ * temp_band_count. Each element has units of VDD supply
++ * steps. Positive values correspond to a reduction in
++ * voltage and negative value correspond to an increase
++ * (this follows the SDELTA register semantics).
++ */
++struct cpr4_sdelta {
++ bool allow_core_count_adj;
++ bool allow_temp_adj;
++ int max_core_count;
++ int temp_band_count;
++ int cap_volt;
++ int *table;
++ bool allow_boost;
++ int boost_num_cores;
++ int *boost_table;
++};
++
++/**
++ * struct cpr3_corner - CPR3 virtual voltage corner data structure
++ * @floor_volt: CPR closed-loop floor voltage in microvolts
++ * @ceiling_volt: CPR closed-loop ceiling voltage in microvolts
++ * @open_loop_volt: CPR open-loop voltage (i.e. initial voltage) in
++ * microvolts
++ * @last_volt: Last known settled CPR closed-loop voltage which is used
++ * when switching to a new corner
++ * @abs_ceiling_volt: The absolute CPR closed-loop ceiling voltage in
++ * microvolts. This is used to limit the ceiling_volt
++ * value when it is increased as a result of aging
++ * adjustment.
++ * @unaged_floor_volt: The CPR closed-loop floor voltage in microvolts before
++ * any aging adjustment is performed
++ * @unaged_ceiling_volt: The CPR closed-loop ceiling voltage in microvolts
++ * before any aging adjustment is performed
++ * @unaged_open_loop_volt: The CPR open-loop voltage (i.e. initial voltage) in
++ * microvolts before any aging adjusment is performed
++ * @system_volt: The system-supply voltage in microvolts or corners or
++ * levels
++ * @mem_acc_volt: The mem-acc-supply voltage in corners
++ * @proc_freq: Processor frequency in Hertz. For CPR rev. 3 and 4
++ * conrollers, this field is only used by platform specific
++ * CPR3 driver for interpolation. For CPRh-compliant
++ * controllers, this frequency is also utilized by the
++ * clock driver to determine the corner to CPU clock
++ * frequency mappings.
++ * @cpr_fuse_corner: Fused corner index associated with this virtual corner
++ * (only used by platform specific CPR3 driver for
++ * mapping purposes)
++ * @target_quot: Array of target quotient values to use for each ring
++ * oscillator (RO) for this corner. A value of 0 should be
++ * specified as the target quotient for each RO that is
++ * unused by this corner.
++ * @ro_scale: Array of CPR ring oscillator (RO) scaling factors. The
++ * scaling factor for each RO is defined from RO0 to RO15
++ * with units of QUOT/V. A value of 0 may be specified for
++ * an RO that is unused.
++ * @ro_mask: Bitmap where each of the 16 LSBs indicate if the
++ * corresponding ROs should be masked for this corner
++ * @irq_en: Bitmap of the CPR interrupts to enable for this corner
++ * @aging_derate: The amount to derate the aging voltage adjustment
++ * determined for the reference corner in units of uV/mV.
++ * E.g. a value of 900 would imply that the adjustment for
++ * this corner should be 90% (900/1000) of that for the
++ * reference corner.
++ * @use_open_loop: Boolean indicating that open-loop (i.e CPR disabled) as
++ * opposed to closed-loop operation must be used for this
++ * corner on CPRh controllers.
++ * @sdelta: The CPR4 controller specific data for this corner. This
++ * field is applicable for CPR4 controllers.
++ *
++ * The value of last_volt is initialized inside of the cpr3_regulator_register()
++ * call with the open_loop_volt value. It can later be updated to the settled
++ * VDD supply voltage. The values for unaged_floor_volt, unaged_ceiling_volt,
++ * and unaged_open_loop_volt are initialized inside of cpr3_regulator_register()
++ * if ctrl->aging_required == true. These three values must be pre-initialized
++ * if cpr3_regulator_register() is called with ctrl->aging_required == false and
++ * ctrl->aging_succeeded == true.
++ *
++ * The values of ro_mask and irq_en are initialized inside of the
++ * cpr3_regulator_register() call.
++ */
++struct cpr3_corner {
++ int floor_volt;
++ int ceiling_volt;
++ int cold_temp_open_loop_volt;
++ int normal_temp_open_loop_volt;
++ int open_loop_volt;
++ int last_volt;
++ int abs_ceiling_volt;
++ int unaged_floor_volt;
++ int unaged_ceiling_volt;
++ int unaged_open_loop_volt;
++ int system_volt;
++ int mem_acc_volt;
++ u32 proc_freq;
++ int cpr_fuse_corner;
++ u32 target_quot[CPR3_RO_COUNT];
++ u32 ro_scale[CPR3_RO_COUNT];
++ u32 ro_mask;
++ u32 irq_en;
++ int aging_derate;
++ bool use_open_loop;
++ struct cpr4_sdelta *sdelta;
++};
++
++/**
++ * struct cprh_corner_band - CPRh controller specific data structure which
++ * encapsulates the range of corners and the SDELTA
++ * adjustment table to be applied to the corners within
++ * the min and max bounds of the corner band.
++ * @corner: Corner number which defines the corner band boundary
++ * @sdelta: The SDELTA adjustment table which contains core-count
++ * and temp based margin adjustments that are applicable
++ * to the corner band.
++ */
++struct cprh_corner_band {
++ int corner;
++ struct cpr4_sdelta *sdelta;
++};
++
++/**
++ * struct cpr3_fuse_parameters - CPR4 fuse specific data structure which has
++ * the required fuse parameters need for Close Loop CPR
++ * @(*apss_ro_sel_param)[2]: Pointer to RO select fuse details
++ * @(*apss_init_voltage_param)[2]: Pointer to Target voltage fuse details
++ * @(*apss_target_quot_param)[2]: Pointer to Target quot fuse details
++ * @(*apss_quot_offset_param)[2]: Pointer to quot offset fuse details
++ * @cpr_fusing_rev_param: Pointer to CPR revision fuse details
++ * @apss_speed_bin_param: Pointer to Speed bin fuse details
++ * @cpr_boost_fuse_cfg_param: Pointer to Boost fuse cfg details
++ * @apss_boost_fuse_volt_param: Pointer to Boost fuse volt details
++ * @misc_fuse_volt_adj_param: Pointer to Misc fuse volt fuse details
++ */
++struct cpr3_fuse_parameters {
++ struct cpr3_fuse_param (*apss_ro_sel_param)[2];
++ struct cpr3_fuse_param (*apss_init_voltage_param)[2];
++ struct cpr3_fuse_param (*apss_target_quot_param)[2];
++ struct cpr3_fuse_param (*apss_quot_offset_param)[2];
++ struct cpr3_fuse_param *cpr_fusing_rev_param;
++ struct cpr3_fuse_param *apss_speed_bin_param;
++ struct cpr3_fuse_param *cpr_boost_fuse_cfg_param;
++ struct cpr3_fuse_param *apss_boost_fuse_volt_param;
++ struct cpr3_fuse_param *misc_fuse_volt_adj_param;
++};
++
++struct cpr4_mem_acc_func {
++ void (*set_mem_acc)(struct regulator_dev *);
++ void (*clear_mem_acc)(struct regulator_dev *);
++};
++
++/**
++ * struct cpr4_reg_data - CPR4 regulator specific data structure which is
++ * target specific
++ * @cpr_valid_fuse_count: Number of valid fuse corners
++ * @fuse_ref_volt: Pointer to fuse reference voltage
++ * @fuse_step_volt: CPR step voltage available in fuse
++ * @cpr_clk_rate: CPR clock rate
++ * @boost_fuse_ref_volt: Boost fuse reference voltage
++ * @boost_ceiling_volt: Boost ceiling voltage
++ * @boost_floor_volt: Boost floor voltage
++ * @cpr3_fuse_params: Pointer to CPR fuse parameters
++ * @mem_acc_funcs: Pointer to MEM ACC set/clear functions
++ **/
++struct cpr4_reg_data {
++ u32 cpr_valid_fuse_count;
++ int *fuse_ref_volt;
++ u32 fuse_step_volt;
++ u32 cpr_clk_rate;
++ int boost_fuse_ref_volt;
++ int boost_ceiling_volt;
++ int boost_floor_volt;
++ struct cpr3_fuse_parameters *cpr3_fuse_params;
++ struct cpr4_mem_acc_func *mem_acc_funcs;
++};
++/**
++ * struct cpr3_reg_data - CPR3 regulator specific data structure which is
++ * target specific
++ * @cpr_valid_fuse_count: Number of valid fuse corners
++ * @(*init_voltage_param)[2]: Pointer to Target voltage fuse details
++ * @fuse_ref_volt: Pointer to fuse reference voltage
++ * @fuse_step_volt: CPR step voltage available in fuse
++ * @cpr_clk_rate: CPR clock rate
++ * @cpr3_fuse_params: Pointer to CPR fuse parameters
++ **/
++struct cpr3_reg_data {
++ u32 cpr_valid_fuse_count;
++ struct cpr3_fuse_param (*init_voltage_param)[2];
++ int *fuse_ref_volt;
++ u32 fuse_step_volt;
++ u32 cpr_clk_rate;
++};
++
++/**
++ * struct cpr3_regulator - CPR3 logical regulator instance associated with a
++ * given CPR3 hardware thread
++ * @of_node: Device node associated with the device tree child node
++ * of this CPR3 regulator
++ * @thread: Pointer to the CPR3 thread which manages this CPR3
++ * regulator
++ * @name: Unique name for this CPR3 regulator which is filled
++ * using the device tree regulator-name property
++ * @rdesc: Regulator description for this CPR3 regulator
++ * @rdev: Regulator device pointer for the regulator registered
++ * for this CPR3 regulator
++ * @mem_acc_regulator: Pointer to the optional mem-acc supply regulator used
++ * to manage memory circuitry settings based upon CPR3
++ * regulator output voltage.
++ * @corner: Array of all corners supported by this CPR3 regulator
++ * @corner_count: The number of elements in the corner array
++ * @corner_band: Array of all corner bands supported by CPRh compatible
++ * controllers
++ * @cpr4_regulator_data Target specific cpr4 regulator data
++ * @cpr3_regulator_data Target specific cpr3 regulator data
++ * @corner_band_count: The number of elements in the corner band array
++ * @platform_fuses: Pointer to platform specific CPR fuse data (only used by
++ * platform specific CPR3 driver)
++ * @speed_bin_fuse: Value read from the speed bin fuse parameter
++ * @speed_bins_supported: The number of speed bins supported by the device tree
++ * configuration for this CPR3 regulator
++ * @cpr_rev_fuse: Value read from the CPR fusing revision fuse parameter
++ * @fuse_combo: Platform specific enum value identifying the specific
++ * combination of fuse values found on a given chip
++ * @fuse_combos_supported: The number of fuse combinations supported by the
++ * device tree configuration for this CPR3 regulator
++ * @fuse_corner_count: Number of corners defined by fuse parameters
++ * @fuse_corner_map: Array of length fuse_corner_count which specifies the
++ * highest corner associated with each fuse corner. Note
++ * that each element must correspond to a valid corner
++ * and that element values must be strictly increasing.
++ * Also, it is acceptable for the lowest fuse corner to map
++ * to a corner other than the lowest. Likewise, it is
++ * acceptable for the highest fuse corner to map to a
++ * corner other than the highest.
++ * @fuse_combo_corner_sum: The sum of the corner counts across all fuse combos
++ * @fuse_combo_offset: The device tree property array offset for the selected
++ * fuse combo
++ * @speed_bin_corner_sum: The sum of the corner counts across all speed bins
++ * This may be specified as 0 if per speed bin parsing
++ * support is not required.
++ * @speed_bin_offset: The device tree property array offset for the selected
++ * speed bin
++ * @fuse_combo_corner_band_sum: The sum of the corner band counts across all
++ * fuse combos
++ * @fuse_combo_corner_band_offset: The device tree property array offset for
++ * the corner band count corresponding to the selected
++ * fuse combo
++ * @speed_bin_corner_band_sum: The sum of the corner band counts across all
++ * speed bins. This may be specified as 0 if per speed bin
++ * parsing support is not required
++ * @speed_bin_corner_band_offset: The device tree property array offset for the
++ * corner band count corresponding to the selected speed
++ * bin
++ * @pd_bypass_mask: Bit mask of power domains associated with this CPR3
++ * regulator
++ * @dynamic_floor_corner: Index identifying the voltage corner for the CPR3
++ * regulator whose last_volt value should be used as the
++ * global CPR floor voltage if all of the power domains
++ * associated with this CPR3 regulator are bypassed
++ * @uses_dynamic_floor: Boolean flag indicating that dynamic_floor_corner should
++ * be utilized for the CPR3 regulator
++ * @current_corner: Index identifying the currently selected voltage corner
++ * for the CPR3 regulator or less than 0 if no corner has
++ * been requested
++ * @last_closed_loop_corner: Index identifying the last voltage corner for the
++ * CPR3 regulator which was configured when operating in
++ * CPR closed-loop mode or less than 0 if no corner has
++ * been requested. CPR registers are only written to when
++ * using closed-loop mode.
++ * @aggregated: Boolean flag indicating that this CPR3 regulator
++ * participated in the last aggregation event
++ * @debug_corner: Index identifying voltage corner used for displaying
++ * corner configuration values in debugfs
++ * @vreg_enabled: Boolean defining the enable state of the CPR3
++ * regulator's regulator within the regulator framework.
++ * @aging_allowed: Boolean defining if CPR aging adjustments are allowed
++ * for this CPR3 regulator given the fuse combo of the
++ * device
++ * @aging_allow_open_loop_adj: Boolean defining if the open-loop voltage of each
++ * corner of this regulator should be adjusted as a result
++ * of an aging measurement. This flag can be set to false
++ * when the open-loop voltage adjustments have been
++ * specified such that they include the maximum possible
++ * aging adjustment. This flag is only used if
++ * aging_allowed == true.
++ * @aging_corner: The corner that should be configured for this regulator
++ * when an aging measurement is performed.
++ * @aging_max_adjust_volt: The maximum aging voltage margin in microvolts that
++ * may be added to the target quotients of this regulator.
++ * A value of 0 may be specified if this regulator does not
++ * require any aging adjustment.
++ * @allow_core_count_adj: Core count adjustments are allowed for this regulator.
++ * @allow_temp_adj: Temperature based adjustments are allowed for this
++ * regulator.
++ * @max_core_count: Maximum number of cores considered for core count
++ * adjustment logic.
++ * @allow_boost: Voltage boost allowed for this regulator.
++ *
++ * This structure contains both configuration and runtime state data. The
++ * elements current_corner, last_closed_loop_corner, aggregated, debug_corner,
++ * and vreg_enabled are state variables.
++ */
++struct cpr3_regulator {
++ struct device_node *of_node;
++ struct cpr3_thread *thread;
++ const char *name;
++ struct regulator_desc rdesc;
++ struct regulator_dev *rdev;
++ struct regulator *mem_acc_regulator;
++ struct cpr3_corner *corner;
++ int corner_count;
++ struct cprh_corner_band *corner_band;
++ struct cpr4_reg_data *cpr4_regulator_data;
++ struct cpr3_reg_data *cpr3_regulator_data;
++ u32 corner_band_count;
++
++ void *platform_fuses;
++ int speed_bin_fuse;
++ int speed_bins_supported;
++ int cpr_rev_fuse;
++ int part_type;
++ int part_type_supported;
++ int fuse_combo;
++ int fuse_combos_supported;
++ int fuse_corner_count;
++ int *fuse_corner_map;
++ int fuse_combo_corner_sum;
++ int fuse_combo_offset;
++ int speed_bin_corner_sum;
++ int speed_bin_offset;
++ int fuse_combo_corner_band_sum;
++ int fuse_combo_corner_band_offset;
++ int speed_bin_corner_band_sum;
++ int speed_bin_corner_band_offset;
++ u32 pd_bypass_mask;
++ int dynamic_floor_corner;
++ bool uses_dynamic_floor;
++
++ int current_corner;
++ int last_closed_loop_corner;
++ bool aggregated;
++ int debug_corner;
++ bool vreg_enabled;
++
++ bool aging_allowed;
++ bool aging_allow_open_loop_adj;
++ int aging_corner;
++ int aging_max_adjust_volt;
++
++ bool allow_core_count_adj;
++ bool allow_temp_adj;
++ int max_core_count;
++ bool allow_boost;
++};
++
++/**
++ * struct cpr3_thread - CPR3 hardware thread data structure
++ * @thread_id: Hardware thread ID
++ * @of_node: Device node associated with the device tree child node
++ * of this CPR3 thread
++ * @ctrl: Pointer to the CPR3 controller which manages this thread
++ * @vreg: Array of CPR3 regulators handled by the CPR3 thread
++ * @vreg_count: Number of elements in the vreg array
++ * @aggr_corner: CPR corner containing the in process aggregated voltage
++ * and target quotient configurations which will be applied
++ * @last_closed_loop_aggr_corner: CPR corner containing the most recent
++ * configurations which were written into hardware
++ * registers when operating in closed loop mode (i.e. with
++ * CPR enabled)
++ * @consecutive_up: The number of consecutive CPR step up events needed to
++ * to trigger an up interrupt
++ * @consecutive_down: The number of consecutive CPR step down events needed to
++ * to trigger a down interrupt
++ * @up_threshold: The number CPR error steps required to generate an up
++ * event
++ * @down_threshold: The number CPR error steps required to generate a down
++ * event
++ *
++ * This structure contains both configuration and runtime state data. The
++ * elements aggr_corner and last_closed_loop_aggr_corner are state variables.
++ */
++struct cpr3_thread {
++ u32 thread_id;
++ struct device_node *of_node;
++ struct cpr3_controller *ctrl;
++ struct cpr3_regulator *vreg;
++ int vreg_count;
++ struct cpr3_corner aggr_corner;
++ struct cpr3_corner last_closed_loop_aggr_corner;
++
++ u32 consecutive_up;
++ u32 consecutive_down;
++ u32 up_threshold;
++ u32 down_threshold;
++};
++
++/* Per CPR controller data */
++/**
++ * enum cpr3_mem_acc_corners - Constants which define the number of mem-acc
++ * regulator corners available in the mem-acc corner map array.
++ * %CPR3_MEM_ACC_LOW_CORNER: Index in mem-acc corner map array mapping to the
++ * mem-acc regulator corner
++ * to be used for low voltage vdd supply
++ * %CPR3_MEM_ACC_HIGH_CORNER: Index in mem-acc corner map array mapping to the
++ * mem-acc regulator corner to be used for high
++ * voltage vdd supply
++ * %CPR3_MEM_ACC_CORNERS: Number of elements in the mem-acc corner map
++ * array
++ */
++enum cpr3_mem_acc_corners {
++ CPR3_MEM_ACC_LOW_CORNER = 0,
++ CPR3_MEM_ACC_HIGH_CORNER = 1,
++ CPR3_MEM_ACC_CORNERS = 2,
++};
++
++/**
++ * enum cpr3_count_mode - CPR3 controller count mode which defines the
++ * method that CPR sensor data is acquired
++ * %CPR3_COUNT_MODE_ALL_AT_ONCE_MIN: Capture all CPR sensor readings
++ * simultaneously and report the minimum
++ * value seen in successive measurements
++ * %CPR3_COUNT_MODE_ALL_AT_ONCE_MAX: Capture all CPR sensor readings
++ * simultaneously and report the maximum
++ * value seen in successive measurements
++ * %CPR3_COUNT_MODE_STAGGERED: Read one sensor at a time in a
++ * sequential fashion
++ * %CPR3_COUNT_MODE_ALL_AT_ONCE_AGE: Capture all CPR aging sensor readings
++ * simultaneously.
++ */
++enum cpr3_count_mode {
++ CPR3_COUNT_MODE_ALL_AT_ONCE_MIN = 0,
++ CPR3_COUNT_MODE_ALL_AT_ONCE_MAX = 1,
++ CPR3_COUNT_MODE_STAGGERED = 2,
++ CPR3_COUNT_MODE_ALL_AT_ONCE_AGE = 3,
++};
++
++/**
++ * enum cpr_controller_type - supported CPR controller hardware types
++ * %CPR_CTRL_TYPE_CPR3: HW has CPR3 controller
++ * %CPR_CTRL_TYPE_CPR4: HW has CPR4 controller
++ */
++enum cpr_controller_type {
++ CPR_CTRL_TYPE_CPR3,
++ CPR_CTRL_TYPE_CPR4,
++};
++
++/**
++ * cpr_setting - supported CPR global settings
++ * %CPR_DEFAULT: default mode from dts will be used
++ * %CPR_DISABLED: ceiling voltage will be used for all the corners
++ * %CPR_OPEN_LOOP_EN: CPR will work in OL
++ * %CPR_CLOSED_LOOP_EN: CPR will work in CL, if supported
++ */
++enum cpr_setting {
++ CPR_DEFAULT = 0,
++ CPR_DISABLED = 1,
++ CPR_OPEN_LOOP_EN = 2,
++ CPR_CLOSED_LOOP_EN = 3,
++};
++
++/**
++ * struct cpr3_aging_sensor_info - CPR3 aging sensor information
++ * @sensor_id The index of the CPR3 sensor to be used in the aging
++ * measurement.
++ * @ro_scale The CPR ring oscillator (RO) scaling factor for the
++ * aging sensor with units of QUOT/V.
++ * @init_quot_diff: The fused quotient difference between aged and un-aged
++ * paths that was measured at manufacturing time.
++ * @measured_quot_diff: The quotient difference measured at runtime.
++ * @bypass_mask: Bit mask of the CPR sensors that must be bypassed during
++ * the aging measurement for this sensor
++ *
++ * This structure contains both configuration and runtime state data. The
++ * element measured_quot_diff is a state variable.
++ */
++struct cpr3_aging_sensor_info {
++ u32 sensor_id;
++ u32 ro_scale;
++ int init_quot_diff;
++ int measured_quot_diff;
++ u32 bypass_mask[CPR3_MAX_SENSOR_COUNT / 32];
++};
++
++/**
++ * struct cpr3_reg_info - Register information data structure
++ * @name: Register name
++ * @addr: Register physical address
++ * @value: Register content
++ * @virt_addr: Register virtual address
++ *
++ * This data structure is used to dump some critical register contents
++ * when the device crashes due to a kernel panic.
++ */
++struct cpr3_reg_info {
++ const char *name;
++ u32 addr;
++ u32 value;
++ void __iomem *virt_addr;
++};
++
++/**
++ * struct cpr3_panic_regs_info - Data structure to dump critical register
++ * contents.
++ * @reg_count: Number of elements in the regs array
++ * @regs: Array of critical registers information
++ *
++ * This data structure is used to dump critical register contents when
++ * the device crashes due to a kernel panic.
++ */
++struct cpr3_panic_regs_info {
++ int reg_count;
++ struct cpr3_reg_info *regs;
++};
++
++/**
++ * struct cpr3_controller - CPR3 controller data structure
++ * @dev: Device pointer for the CPR3 controller device
++ * @name: Unique name for the CPR3 controller
++ * @ctrl_id: Controller ID corresponding to the VDD supply number
++ * that this CPR3 controller manages.
++ * @cpr_ctrl_base: Virtual address of the CPR3 controller base register
++ * @fuse_base: Virtual address of fuse row 0
++ * @aging_possible_reg: Virtual address of an optional platform-specific
++ * register that must be ready to determine if it is
++ * possible to perform an aging measurement.
++ * @list: list head used in a global cpr3-regulator list so that
++ * cpr3-regulator structs can be found easily in RAM dumps
++ * @thread: Array of CPR3 threads managed by the CPR3 controller
++ * @thread_count: Number of elements in the thread array
++ * @sensor_owner: Array of thread IDs indicating which thread owns a given
++ * CPR sensor
++ * @sensor_count: The number of CPR sensors found on the CPR loop managed
++ * by this CPR controller. Must be equal to the number of
++ * elements in the sensor_owner array
++ * @soc_revision: Revision number of the SoC. This may be unused by
++ * platforms that do not have different behavior for
++ * different SoC revisions.
++ * @lock: Mutex lock used to ensure mutual exclusion between
++ * all of the threads associated with the controller
++ * @vdd_regulator: Pointer to the VDD supply regulator which this CPR3
++ * controller manages
++ * @system_regulator: Pointer to the optional system-supply regulator upon
++ * which the VDD supply regulator depends.
++ * @mem_acc_regulator: Pointer to the optional mem-acc supply regulator used
++ * to manage memory circuitry settings based upon the
++ * VDD supply output voltage.
++ * @vdd_limit_regulator: Pointer to the VDD supply limit regulator which is used
++ * for hardware closed-loop in order specify ceiling and
++ * floor voltage limits (platform specific)
++ * @system_supply_max_volt: Voltage in microvolts which corresponds to the
++ * absolute ceiling voltage of the system-supply
++ * @mem_acc_threshold_volt: mem-acc threshold voltage in microvolts
++ * @mem_acc_corner_map: mem-acc regulator corners mapping to low and high
++ * voltage mem-acc settings for the memories powered by
++ * this CPR3 controller and its associated CPR3 regulators
++ * @mem_acc_crossover_volt: Voltage in microvolts corresponding to the voltage
++ * that the VDD supply must be set to while a MEM ACC
++ * switch is in progress. This element must be initialized
++ * for CPRh controllers when a MEM ACC threshold voltage is
++ * defined.
++ * @core_clk: Pointer to the CPR3 controller core clock
++ * @iface_clk: Pointer to the CPR3 interface clock (platform specific)
++ * @bus_clk: Pointer to the CPR3 bus clock (platform specific)
++ * @irq: CPR interrupt number
++ * @irq_affinity_mask: The cpumask for the CPUs which the CPR interrupt should
++ * have affinity for
++ * @cpu_hotplug_notifier: CPU hotplug notifier used to reset IRQ affinity when a
++ * CPU is brought back online
++ * @ceiling_irq: Interrupt number for the interrupt that is triggered
++ * when hardware closed-loop attempts to exceed the ceiling
++ * voltage
++ * @apm: Handle to the array power mux (APM)
++ * @apm_threshold_volt: Voltage in microvolts which defines the threshold
++ * voltage to determine the APM supply selection for
++ * each corner
++ * @apm_crossover_volt: Voltage in microvolts corresponding to the voltage that
++ * the VDD supply must be set to while an APM switch is in
++ * progress. This element must be initialized for CPRh
++ * controllers when an APM threshold voltage is defined
++ * @apm_adj_volt: Minimum difference between APM threshold voltage and
++ * open-loop voltage which allows the APM threshold voltage
++ * to be used as a ceiling
++ * @apm_high_supply: APM supply to configure if VDD voltage is greater than
++ * or equal to the APM threshold voltage
++ * @apm_low_supply: APM supply to configure if the VDD voltage is less than
++ * the APM threshold voltage
++ * @base_volt: Minimum voltage in microvolts supported by the VDD
++ * supply managed by this CPR controller
++ * @corner_switch_delay_time: The delay time in nanoseconds used by the CPR
++ * controller to wait for voltage settling before
++ * acknowledging the OSM block after corner changes
++ * @cpr_clock_rate: CPR reference clock frequency in Hz.
++ * @sensor_time: The time in nanoseconds that each sensor takes to
++ * perform a measurement.
++ * @loop_time: The time in nanoseconds between consecutive CPR
++ * measurements.
++ * @up_down_delay_time: The time to delay in nanoseconds between consecutive CPR
++ * measurements when the last measurement recommended
++ * increasing or decreasing the vdd-supply voltage.
++ * (platform specific)
++ * @idle_clocks: Number of CPR reference clock ticks that the CPR
++ * controller waits in transitional states.
++ * @step_quot_init_min: The default minimum CPR step quotient value. The step
++ * quotient is the number of additional ring oscillator
++ * ticks observed when increasing one step in vdd-supply
++ * output voltage.
++ * @step_quot_init_max: The default maximum CPR step quotient value.
++ * @step_volt: Step size in microvolts between available set points
++ * of the VDD supply
++ * @down_error_step_limit: CPR4 hardware closed-loop down error step limit which
++ * defines the maximum number of VDD supply regulator steps
++ * that the voltage may be reduced as the result of a
++ * single CPR measurement.
++ * @up_error_step_limit: CPR4 hardware closed-loop up error step limit which
++ * defines the maximum number of VDD supply regulator steps
++ * that the voltage may be increased as the result of a
++ * single CPR measurement.
++ * @count_mode: CPR controller count mode
++ * @count_repeat: Number of times to perform consecutive sensor
++ * measurements when using all-at-once count modes.
++ * @proc_clock_throttle: Defines the processor clock frequency throttling
++ * register value to use. This can be used to reduce the
++ * clock frequency when a power domain exits a low power
++ * mode until CPR settles at a new voltage.
++ * (platform specific)
++ * @cpr_allowed_hw: Boolean which indicates if closed-loop CPR operation is
++ * permitted for a given chip based upon hardware fuse
++ * values
++ * @cpr_allowed_sw: Boolean which indicates if closed-loop CPR operation is
++ * permitted based upon software policies
++ * @supports_hw_closed_loop: Boolean which indicates if this CPR3/4 controller
++ * physically supports hardware closed-loop CPR operation
++ * @use_hw_closed_loop: Boolean which indicates that this controller will be
++ * using hardware closed-loop operation in place of
++ * software closed-loop operation.
++ * @ctrl_type: CPR controller type
++ * @saw_use_unit_mV: Boolean which indicates the unit used in SAW PVC
++ * interface is mV.
++ * @aggr_corner: CPR corner containing the most recently aggregated
++ * voltage configurations which are being used currently
++ * @cpr_enabled: Boolean which indicates that the CPR controller is
++ * enabled and operating in closed-loop mode. CPR clocks
++ * have been prepared and enabled whenever this flag is
++ * true.
++ * @last_corner_was_closed_loop: Boolean indicating if the last known corners
++ * were updated during closed loop operation.
++ * @cpr_suspended: Boolean which indicates that CPR has been temporarily
++ * disabled while enterring system suspend.
++ * @debugfs: Pointer to the debugfs directory of this CPR3 controller
++ * @aging_ref_volt: Reference voltage in microvolts to configure when
++ * performing CPR aging measurements.
++ * @aging_vdd_mode: vdd-supply regulator mode to configure before performing
++ * a CPR aging measurement. It should be one of
++ * REGULATOR_MODE_*.
++ * @aging_complete_vdd_mode: vdd-supply regulator mode to configure after
++ * performing a CPR aging measurement. It should be one of
++ * REGULATOR_MODE_*.
++ * @aging_ref_adjust_volt: The reference aging voltage margin in microvolts that
++ * should be added to the target quotients of the
++ * regulators managed by this controller after derating.
++ * @aging_required: Flag which indicates that a CPR aging measurement still
++ * needs to be performed for this CPR3 controller.
++ * @aging_succeeded: Flag which indicates that a CPR aging measurement has
++ * completed successfully.
++ * @aging_failed: Flag which indicates that a CPR aging measurement has
++ * failed to complete successfully.
++ * @aging_sensor: Array of CPR3 aging sensors which are used to perform
++ * aging measurements at a runtime.
++ * @aging_sensor_count: Number of elements in the aging_sensor array
++ * @aging_possible_mask: Optional bitmask used to mask off the
++ * aging_possible_reg register.
++ * @aging_possible_val: Optional value that the masked aging_possible_reg
++ * register must have in order for a CPR aging measurement
++ * to be possible.
++ * @step_quot_fixed: Fixed step quotient value used for target quotient
++ * adjustment if use_dynamic_step_quot is not set.
++ * This parameter is only relevant for CPR4 controllers
++ * when using the per-online-core or per-temperature
++ * adjustments.
++ * @initial_temp_band: Temperature band used for calculation of base-line
++ * target quotients (fused).
++ * @use_dynamic_step_quot: Boolean value which indicates that margin adjustment
++ * of target quotient will be based on the step quotient
++ * calculated dynamically in hardware for each RO.
++ * @allow_core_count_adj: Core count adjustments are allowed for this controller
++ * @allow_temp_adj: Temperature based adjustments are allowed for
++ * this controller
++ * @allow_boost: Voltage boost allowed for this controller.
++ * @temp_band_count: Number of temperature bands used for temperature based
++ * adjustment logic
++ * @temp_points: Array of temperature points in decidegrees Celsius used
++ * to specify the ranges for selected temperature bands.
++ * The array must have (temp_band_count - 1) elements
++ * allocated.
++ * @temp_sensor_id_start: Start ID of temperature sensors used for temperature
++ * based adjustments.
++ * @temp_sensor_id_end: End ID of temperature sensors used for temperature
++ * based adjustments.
++ * @voltage_settling_time: The time in nanoseconds that it takes for the
++ * VDD supply voltage to settle after being increased or
++ * decreased by step_volt microvolts which is used when
++ * SDELTA voltage margin adjustments are applied.
++ * @cpr_global_setting: Global setting for this CPR controller
++ * @panic_regs_info: Array of panic registers information which provides the
++ * list of registers to dump when the device crashes.
++ * @panic_notifier: Notifier block registered to global panic notifier list.
++ *
++ * This structure contains both configuration and runtime state data. The
++ * elements cpr_allowed_sw, use_hw_closed_loop, aggr_corner, cpr_enabled,
++ * last_corner_was_closed_loop, cpr_suspended, aging_ref_adjust_volt,
++ * aging_required, aging_succeeded, and aging_failed are state variables.
++ *
++ * The apm* elements do not need to be initialized if the VDD supply managed by
++ * the CPR3 controller does not utilize an APM.
++ *
++ * The elements step_quot_fixed, initial_temp_band, allow_core_count_adj,
++ * allow_temp_adj and temp* need to be initialized for CPR4 controllers which
++ * are using per-online-core or per-temperature adjustments.
++ */
++struct cpr3_controller {
++ struct device *dev;
++ const char *name;
++ int ctrl_id;
++ void __iomem *cpr_ctrl_base;
++ void __iomem *fuse_base;
++ void __iomem *aging_possible_reg;
++ struct list_head list;
++ struct cpr3_thread *thread;
++ int thread_count;
++ u8 *sensor_owner;
++ int sensor_count;
++ int soc_revision;
++ struct mutex lock;
++ struct regulator *vdd_regulator;
++ struct regulator *system_regulator;
++ struct regulator *mem_acc_regulator;
++ struct regulator *vdd_limit_regulator;
++ int system_supply_max_volt;
++ int mem_acc_threshold_volt;
++ int mem_acc_corner_map[CPR3_MEM_ACC_CORNERS];
++ int mem_acc_crossover_volt;
++ struct clk *core_clk;
++ struct clk *iface_clk;
++ struct clk *bus_clk;
++ int irq;
++ struct cpumask irq_affinity_mask;
++ struct notifier_block cpu_hotplug_notifier;
++ int ceiling_irq;
++ struct msm_apm_ctrl_dev *apm;
++ int apm_threshold_volt;
++ int apm_crossover_volt;
++ int apm_adj_volt;
++ enum msm_apm_supply apm_high_supply;
++ enum msm_apm_supply apm_low_supply;
++ int base_volt;
++ u32 corner_switch_delay_time;
++ u32 cpr_clock_rate;
++ u32 sensor_time;
++ u32 loop_time;
++ u32 up_down_delay_time;
++ u32 idle_clocks;
++ u32 step_quot_init_min;
++ u32 step_quot_init_max;
++ int step_volt;
++ u32 down_error_step_limit;
++ u32 up_error_step_limit;
++ enum cpr3_count_mode count_mode;
++ u32 count_repeat;
++ u32 proc_clock_throttle;
++ bool cpr_allowed_hw;
++ bool cpr_allowed_sw;
++ bool supports_hw_closed_loop;
++ bool use_hw_closed_loop;
++ enum cpr_controller_type ctrl_type;
++ bool saw_use_unit_mV;
++ struct cpr3_corner aggr_corner;
++ bool cpr_enabled;
++ bool last_corner_was_closed_loop;
++ bool cpr_suspended;
++ struct dentry *debugfs;
++
++ int aging_ref_volt;
++ unsigned int aging_vdd_mode;
++ unsigned int aging_complete_vdd_mode;
++ int aging_ref_adjust_volt;
++ bool aging_required;
++ bool aging_succeeded;
++ bool aging_failed;
++ struct cpr3_aging_sensor_info *aging_sensor;
++ int aging_sensor_count;
++ u32 cur_sensor_state;
++ u32 aging_possible_mask;
++ u32 aging_possible_val;
++
++ u32 step_quot_fixed;
++ u32 initial_temp_band;
++ bool use_dynamic_step_quot;
++ bool allow_core_count_adj;
++ bool allow_temp_adj;
++ bool allow_boost;
++ int temp_band_count;
++ int *temp_points;
++ u32 temp_sensor_id_start;
++ u32 temp_sensor_id_end;
++ u32 voltage_settling_time;
++ enum cpr_setting cpr_global_setting;
++ struct cpr3_panic_regs_info *panic_regs_info;
++ struct notifier_block panic_notifier;
++};
++
++/* Used for rounding voltages to the closest physically available set point. */
++#define CPR3_ROUND(n, d) (DIV_ROUND_UP(n, d) * (d))
++
++#define cpr3_err(cpr3_thread, message, ...) \
++ pr_err("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
++#define cpr3_info(cpr3_thread, message, ...) \
++ pr_info("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
++#define cpr3_debug(cpr3_thread, message, ...) \
++ pr_debug("%s: " message, (cpr3_thread)->name, ##__VA_ARGS__)
++
++/*
++ * Offset subtracted from voltage corner values passed in from the regulator
++ * framework in order to get internal voltage corner values. This is needed
++ * since the regulator framework treats 0 as an error value at regulator
++ * registration time.
++ */
++#define CPR3_CORNER_OFFSET 1
++
++#ifdef CONFIG_REGULATOR_CPR3
++
++int cpr3_regulator_register(struct platform_device *pdev,
++ struct cpr3_controller *ctrl);
++int cpr3_open_loop_regulator_register(struct platform_device *pdev,
++ struct cpr3_controller *ctrl);
++int cpr3_regulator_unregister(struct cpr3_controller *ctrl);
++int cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl);
++int cpr3_regulator_suspend(struct cpr3_controller *ctrl);
++int cpr3_regulator_resume(struct cpr3_controller *ctrl);
++
++int cpr3_allocate_threads(struct cpr3_controller *ctrl, u32 min_thread_id,
++ u32 max_thread_id);
++int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
++ struct platform_device *pdev);
++int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
++ struct platform_device *pdev, u8 start, u8 end);
++int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
++ const struct cpr3_fuse_param *param, u64 *param_value);
++int cpr3_convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse,
++ int fuse_len);
++u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x);
++int cpr3_parse_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out);
++int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out);
++int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out);
++int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg);
++int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
++ u32 *out_value, u32 value_min, u32 value_max);
++int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl, const char *propname,
++ u32 *out_value, u32 value_min, u32 value_max);
++int cpr3_parse_common_thread_data(struct cpr3_thread *thread);
++int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl);
++int cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl);
++int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg);
++void cpr3_open_loop_voltage_as_ceiling(struct cpr3_regulator *vreg);
++int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg);
++void cpr3_print_quots(struct cpr3_regulator *vreg);
++int cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt);
++int cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
++ int *fuse_volt);
++int cpr3_adjust_fused_open_loop_voltages(struct cpr3_regulator *vreg,
++ int *fuse_volt);
++int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg);
++int cpr3_quot_adjustment(int ro_scale, int volt_adjust);
++int cpr3_voltage_adjustment(int ro_scale, int quot_adjust);
++int cpr3_parse_closed_loop_voltage_adjustments(struct cpr3_regulator *vreg,
++ u64 *ro_sel, int *volt_adjust,
++ int *volt_adjust_fuse, int *ro_scale);
++int cpr4_parse_core_count_temp_voltage_adj(struct cpr3_regulator *vreg,
++ bool use_corner_band);
++int cpr3_apm_init(struct cpr3_controller *ctrl);
++int cpr3_mem_acc_init(struct cpr3_regulator *vreg);
++void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg);
++void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg);
++int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
++ int *fuse_volt_adjust);
++int cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
++ bool is_cold);
++int cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp);
++bool cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg);
++
++#else
++
++static inline int cpr3_regulator_register(struct platform_device *pdev,
++ struct cpr3_controller *ctrl)
++{
++ return -ENXIO;
++}
++
++static inline int
++cpr3_open_loop_regulator_register(struct platform_device *pdev,
++ struct cpr3_controller *ctrl);
++{
++ return -ENXIO;
++}
++
++static inline int cpr3_regulator_unregister(struct cpr3_controller *ctrl)
++{
++ return -ENXIO;
++}
++
++static inline int
++cpr3_open_loop_regulator_unregister(struct cpr3_controller *ctrl)
++{
++ return -ENXIO;
++}
++
++static inline int cpr3_regulator_suspend(struct cpr3_controller *ctrl)
++{
++ return -ENXIO;
++}
++
++static inline int cpr3_regulator_resume(struct cpr3_controller *ctrl)
++{
++ return -ENXIO;
++}
++
++static inline int cpr3_get_thread_name(struct cpr3_thread *thread,
++ struct device_node *thread_node)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_allocate_threads(struct cpr3_controller *ctrl,
++ u32 min_thread_id, u32 max_thread_id)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
++ struct platform_device *pdev)
++{
++ return -ENXIO;
++}
++
++static inline int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
++ struct platform_device *pdev, u8 start, u8 end)
++{
++ return 0;
++}
++
++static inline int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
++ const struct cpr3_fuse_param *param, u64 *param_value)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_convert_open_loop_voltage_fuse(int ref_volt,
++ int step_volt, u32 fuse, int fuse_len)
++{
++ return -EPERM;
++}
++
++static inline u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x)
++{
++ return 0;
++}
++
++static inline int cpr3_parse_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_corner_band_array_property(
++ struct cpr3_regulator *vreg, const char *prop_name,
++ int tuple_size, u32 *out)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_thread_u32(struct cpr3_thread *thread,
++ const char *propname, u32 *out_value, u32 value_min,
++ u32 value_max)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl,
++ const char *propname, u32 *out_value, u32 value_min,
++ u32 value_max)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_common_thread_data(struct cpr3_thread *thread)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
++{
++ return -EPERM;
++}
++
++static inline int
++cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg)
++{
++ return -EPERM;
++}
++
++static inline void cpr3_open_loop_voltage_as_ceiling(
++ struct cpr3_regulator *vreg)
++{
++ return;
++}
++
++static inline int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
++{
++ return -EPERM;
++}
++
++static inline void cpr3_print_quots(struct cpr3_regulator *vreg)
++{
++ return;
++}
++
++static inline int
++cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt)
++{
++ return -EPERM;
++}
++
++static inline int
++cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
++ int *fuse_volt)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_adjust_fused_open_loop_voltages(
++ struct cpr3_regulator *vreg, int *fuse_volt)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg)
++{
++ return -EPERM;
++}
++
++static inline int cpr3_quot_adjustment(int ro_scale, int volt_adjust)
++{
++ return 0;
++}
++
++static inline int cpr3_voltage_adjustment(int ro_scale, int quot_adjust)
++{
++ return 0;
++}
++
++static inline int cpr3_parse_closed_loop_voltage_adjustments(
++ struct cpr3_regulator *vreg, u64 *ro_sel,
++ int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
++{
++ return 0;
++}
++
++static inline int cpr4_parse_core_count_temp_voltage_adj(
++ struct cpr3_regulator *vreg, bool use_corner_band)
++{
++ return 0;
++}
++
++static inline int cpr3_apm_init(struct cpr3_controller *ctrl)
++{
++ return 0;
++}
++
++static inline int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
++{
++ return 0;
++}
++
++static inline void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
++{
++}
++
++static inline void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg)
++{
++}
++
++static inline int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
++ int *fuse_volt_adjust)
++{
++ return 0;
++}
++
++static inline int
++cpr3_handle_temp_open_loop_adjustment(struct cpr3_controller *ctrl,
++ bool is_cold)
++{
++ return 0;
++}
++
++static inline bool
++cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg)
++{
++ return false;
++}
++
++static inline int
++cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp)
++{
++ return 0;
++}
++#endif /* CONFIG_REGULATOR_CPR3 */
++
++#endif /* __REGULATOR_CPR_REGULATOR_H__ */
+--- /dev/null
++++ b/drivers/regulator/cpr3-util.c
+@@ -0,0 +1,2750 @@
++/*
++ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++/*
++ * This file contains utility functions to be used by platform specific CPR3
++ * regulator drivers.
++ */
++
++#define pr_fmt(fmt) "%s: " fmt, __func__
++
++#include <linux/cpumask.h>
++#include <linux/device.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include <soc/qcom/socinfo.h>
++
++#include "cpr3-regulator.h"
++
++#define BYTES_PER_FUSE_ROW 8
++#define MAX_FUSE_ROW_BIT 63
++
++#define CPR3_CONSECUTIVE_UP_DOWN_MIN 0
++#define CPR3_CONSECUTIVE_UP_DOWN_MAX 15
++#define CPR3_UP_DOWN_THRESHOLD_MIN 0
++#define CPR3_UP_DOWN_THRESHOLD_MAX 31
++#define CPR3_STEP_QUOT_MIN 0
++#define CPR3_STEP_QUOT_MAX 63
++#define CPR3_IDLE_CLOCKS_MIN 0
++#define CPR3_IDLE_CLOCKS_MAX 31
++
++/* This constant has units of uV/mV so 1000 corresponds to 100%. */
++#define CPR3_AGING_DERATE_UNITY 1000
++
++/**
++ * cpr3_allocate_regulators() - allocate and initialize CPR3 regulators for a
++ * given thread based upon device tree data
++ * @thread: Pointer to the CPR3 thread
++ *
++ * This function allocates the thread->vreg array based upon the number of
++ * device tree regulator subnodes. It also initializes generic elements of each
++ * regulator struct such as name, of_node, and thread.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_allocate_regulators(struct cpr3_thread *thread)
++{
++ struct device_node *node;
++ int i, rc;
++
++ thread->vreg_count = 0;
++
++ for_each_available_child_of_node(thread->of_node, node) {
++ thread->vreg_count++;
++ }
++
++ thread->vreg = devm_kcalloc(thread->ctrl->dev, thread->vreg_count,
++ sizeof(*thread->vreg), GFP_KERNEL);
++ if (!thread->vreg)
++ return -ENOMEM;
++
++ i = 0;
++ for_each_available_child_of_node(thread->of_node, node) {
++ thread->vreg[i].of_node = node;
++ thread->vreg[i].thread = thread;
++
++ rc = of_property_read_string(node, "regulator-name",
++ &thread->vreg[i].name);
++ if (rc) {
++ dev_err(thread->ctrl->dev, "could not find regulator name, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ i++;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_allocate_threads() - allocate and initialize CPR3 threads for a given
++ * controller based upon device tree data
++ * @ctrl: Pointer to the CPR3 controller
++ * @min_thread_id: Minimum allowed hardware thread ID for this controller
++ * @max_thread_id: Maximum allowed hardware thread ID for this controller
++ *
++ * This function allocates the ctrl->thread array based upon the number of
++ * device tree thread subnodes. It also initializes generic elements of each
++ * thread struct such as thread_id, of_node, ctrl, and vreg array.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_allocate_threads(struct cpr3_controller *ctrl, u32 min_thread_id,
++ u32 max_thread_id)
++{
++ struct device *dev = ctrl->dev;
++ struct device_node *thread_node;
++ int i, j, rc;
++
++ ctrl->thread_count = 0;
++
++ for_each_available_child_of_node(dev->of_node, thread_node) {
++ ctrl->thread_count++;
++ }
++
++ ctrl->thread = devm_kcalloc(dev, ctrl->thread_count,
++ sizeof(*ctrl->thread), GFP_KERNEL);
++ if (!ctrl->thread)
++ return -ENOMEM;
++
++ i = 0;
++ for_each_available_child_of_node(dev->of_node, thread_node) {
++ ctrl->thread[i].of_node = thread_node;
++ ctrl->thread[i].ctrl = ctrl;
++
++ rc = of_property_read_u32(thread_node, "qcom,cpr-thread-id",
++ &ctrl->thread[i].thread_id);
++ if (rc) {
++ dev_err(dev, "could not read DT property qcom,cpr-thread-id, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (ctrl->thread[i].thread_id < min_thread_id ||
++ ctrl->thread[i].thread_id > max_thread_id) {
++ dev_err(dev, "invalid thread id = %u; not within [%u, %u]\n",
++ ctrl->thread[i].thread_id, min_thread_id,
++ max_thread_id);
++ return -EINVAL;
++ }
++
++ /* Verify that the thread ID is unique for all child nodes. */
++ for (j = 0; j < i; j++) {
++ if (ctrl->thread[j].thread_id
++ == ctrl->thread[i].thread_id) {
++ dev_err(dev, "duplicate thread id = %u found\n",
++ ctrl->thread[i].thread_id);
++ return -EINVAL;
++ }
++ }
++
++ rc = cpr3_allocate_regulators(&ctrl->thread[i]);
++ if (rc)
++ return rc;
++
++ i++;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_map_fuse_base() - ioremap the base address of the fuse region
++ * @ctrl: Pointer to the CPR3 controller
++ * @pdev: Platform device pointer for the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_map_fuse_base(struct cpr3_controller *ctrl,
++ struct platform_device *pdev)
++{
++ struct resource *res;
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fuse_base");
++ if (!res || !res->start) {
++ dev_err(&pdev->dev, "fuse base address is missing\n");
++ return -ENXIO;
++ }
++
++ ctrl->fuse_base = devm_ioremap(&pdev->dev, res->start,
++ resource_size(res));
++
++ return 0;
++}
++
++/**
++ * cpr3_read_tcsr_setting - reads the CPR setting bits from TCSR register
++ * @ctrl: Pointer to the CPR3 controller
++ * @pdev: Platform device pointer for the CPR3 controller
++ * @start: start bit in TCSR register
++ * @end: end bit in TCSR register
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_read_tcsr_setting(struct cpr3_controller *ctrl,
++ struct platform_device *pdev, u8 start, u8 end)
++{
++ struct resource *res;
++ void __iomem *tcsr_reg;
++ u32 val;
++
++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
++ "cpr_tcsr_reg");
++ if (!res || !res->start)
++ return 0;
++
++ tcsr_reg = ioremap(res->start, resource_size(res));
++ if (!tcsr_reg) {
++ dev_err(&pdev->dev, "tcsr ioremap failed\n");
++ return 0;
++ }
++
++ val = readl_relaxed(tcsr_reg);
++ val &= GENMASK(end, start);
++ val >>= start;
++
++ switch (val) {
++ case 1:
++ ctrl->cpr_global_setting = CPR_DISABLED;
++ break;
++ case 2:
++ ctrl->cpr_global_setting = CPR_OPEN_LOOP_EN;
++ break;
++ case 3:
++ ctrl->cpr_global_setting = CPR_CLOSED_LOOP_EN;
++ break;
++ default:
++ ctrl->cpr_global_setting = CPR_DEFAULT;
++ }
++
++ iounmap(tcsr_reg);
++
++ return 0;
++}
++
++/**
++ * cpr3_read_fuse_param() - reads a CPR3 fuse parameter out of eFuses
++ * @fuse_base_addr: Virtual memory address of the eFuse base address
++ * @param: Null terminated array of fuse param segments to read
++ * from
++ * @param_value: Output with value read from the eFuses
++ *
++ * This function reads from each of the parameter segments listed in the param
++ * array and concatenates their values together. Reading stops when an element
++ * is reached which has all 0 struct values. The total number of bits specified
++ * for the fuse parameter across all segments must be less than or equal to 64.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_read_fuse_param(void __iomem *fuse_base_addr,
++ const struct cpr3_fuse_param *param, u64 *param_value)
++{
++ u64 fuse_val, val;
++ int bits;
++ int bits_total = 0;
++
++ *param_value = 0;
++
++ while (param->row || param->bit_start || param->bit_end) {
++ if (param->bit_start > param->bit_end
++ || param->bit_end > MAX_FUSE_ROW_BIT) {
++ pr_err("Invalid fuse parameter segment: row=%u, start=%u, end=%u\n",
++ param->row, param->bit_start, param->bit_end);
++ return -EINVAL;
++ }
++
++ bits = param->bit_end - param->bit_start + 1;
++ if (bits_total + bits > 64) {
++ pr_err("Invalid fuse parameter segments; total bits = %d\n",
++ bits_total + bits);
++ return -EINVAL;
++ }
++
++ fuse_val = readq_relaxed(fuse_base_addr
++ + param->row * BYTES_PER_FUSE_ROW);
++ val = (fuse_val >> param->bit_start) & ((1ULL << bits) - 1);
++ *param_value |= val << bits_total;
++ bits_total += bits;
++
++ param++;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_convert_open_loop_voltage_fuse() - converts an open loop voltage fuse
++ * value into an absolute voltage with units of microvolts
++ * @ref_volt: Reference voltage in microvolts
++ * @step_volt: The step size in microvolts of the fuse LSB
++ * @fuse: Open loop voltage fuse value
++ * @fuse_len: The bit length of the fuse value
++ *
++ * The MSB of the fuse parameter corresponds to a sign bit. If it is set, then
++ * the lower bits correspond to the number of steps to go down from the
++ * reference voltage. If it is not set, then the lower bits correspond to the
++ * number of steps to go up from the reference voltage.
++ */
++int cpr3_convert_open_loop_voltage_fuse(int ref_volt, int step_volt, u32 fuse,
++ int fuse_len)
++{
++ int sign, steps;
++
++ sign = (fuse & (1 << (fuse_len - 1))) ? -1 : 1;
++ steps = fuse & ((1 << (fuse_len - 1)) - 1);
++
++ return ref_volt + sign * steps * step_volt;
++}
++
++/**
++ * cpr3_interpolate() - performs linear interpolation
++ * @x1 Lower known x value
++ * @y1 Lower known y value
++ * @x2 Upper known x value
++ * @y2 Upper known y value
++ * @x Intermediate x value
++ *
++ * Returns y where (x, y) falls on the line between (x1, y1) and (x2, y2).
++ * It is required that x1 < x2, y1 <= y2, and x1 <= x <= x2. If these
++ * conditions are not met, then y2 will be returned.
++ */
++u64 cpr3_interpolate(u64 x1, u64 y1, u64 x2, u64 y2, u64 x)
++{
++ u64 temp;
++
++ if (x1 >= x2 || y1 > y2 || x1 > x || x > x2)
++ return y2;
++
++ temp = (x2 - x) * (y2 - y1);
++ do_div(temp, (u32)(x2 - x1));
++
++ return y2 - temp;
++}
++
++/**
++ * cpr3_parse_array_property() - fill an array from a portion of the values
++ * specified for a device tree property
++ * @vreg: Pointer to the CPR3 regulator
++ * @prop_name: The name of the device tree property to read from
++ * @tuple_size: The number of elements in each tuple
++ * @out: Output data array which must be of size tuple_size
++ *
++ * cpr3_parse_common_corner_data() must be called for vreg before this function
++ * is called so that fuse combo and speed bin size elements are initialized.
++ *
++ * Three formats are supported for the device tree property:
++ * 1. Length == tuple_size
++ * (reading begins at index 0)
++ * 2. Length == tuple_size * vreg->fuse_combos_supported
++ * (reading begins at index tuple_size * vreg->fuse_combo)
++ * 3. Length == tuple_size * vreg->speed_bins_supported
++ * (reading begins at index tuple_size * vreg->speed_bin_fuse)
++ *
++ * All other property lengths are treated as errors.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out)
++{
++ struct device_node *node = vreg->of_node;
++ int len = 0;
++ int i, offset, rc;
++
++ if (!of_find_property(node, prop_name, &len)) {
++ cpr3_err(vreg, "property %s is missing\n", prop_name);
++ return -EINVAL;
++ }
++
++ if (len == tuple_size * sizeof(u32)) {
++ offset = 0;
++ } else if (len == tuple_size * vreg->fuse_combos_supported
++ * sizeof(u32)) {
++ offset = tuple_size * vreg->fuse_combo;
++ } else if (vreg->speed_bins_supported > 0 &&
++ len == tuple_size * vreg->speed_bins_supported * sizeof(u32)) {
++ offset = tuple_size * vreg->speed_bin_fuse;
++ } else {
++ if (vreg->speed_bins_supported > 0)
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
++ prop_name, len,
++ tuple_size * sizeof(u32),
++ tuple_size * vreg->speed_bins_supported
++ * sizeof(u32),
++ tuple_size * vreg->fuse_combos_supported
++ * sizeof(u32));
++ else
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
++ prop_name, len,
++ tuple_size * sizeof(u32),
++ tuple_size * vreg->fuse_combos_supported
++ * sizeof(u32));
++ return -EINVAL;
++ }
++
++ for (i = 0; i < tuple_size; i++) {
++ rc = of_property_read_u32_index(node, prop_name, offset + i,
++ &out[i]);
++ if (rc) {
++ cpr3_err(vreg, "error reading property %s, rc=%d\n",
++ prop_name, rc);
++ return rc;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_parse_corner_array_property() - fill a per-corner array from a portion
++ * of the values specified for a device tree property
++ * @vreg: Pointer to the CPR3 regulator
++ * @prop_name: The name of the device tree property to read from
++ * @tuple_size: The number of elements in each per-corner tuple
++ * @out: Output data array which must be of size:
++ * tuple_size * vreg->corner_count
++ *
++ * cpr3_parse_common_corner_data() must be called for vreg before this function
++ * is called so that fuse combo and speed bin size elements are initialized.
++ *
++ * Three formats are supported for the device tree property:
++ * 1. Length == tuple_size * vreg->corner_count
++ * (reading begins at index 0)
++ * 2. Length == tuple_size * vreg->fuse_combo_corner_sum
++ * (reading begins at index tuple_size * vreg->fuse_combo_offset)
++ * 3. Length == tuple_size * vreg->speed_bin_corner_sum
++ * (reading begins at index tuple_size * vreg->speed_bin_offset)
++ *
++ * All other property lengths are treated as errors.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_corner_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out)
++{
++ struct device_node *node = vreg->of_node;
++ int len = 0;
++ int i, offset, rc;
++
++ if (!of_find_property(node, prop_name, &len)) {
++ cpr3_err(vreg, "property %s is missing\n", prop_name);
++ return -EINVAL;
++ }
++
++ if (len == tuple_size * vreg->corner_count * sizeof(u32)) {
++ offset = 0;
++ } else if (len == tuple_size * vreg->fuse_combo_corner_sum
++ * sizeof(u32)) {
++ offset = tuple_size * vreg->fuse_combo_offset;
++ } else if (vreg->speed_bin_corner_sum > 0 &&
++ len == tuple_size * vreg->speed_bin_corner_sum * sizeof(u32)) {
++ offset = tuple_size * vreg->speed_bin_offset;
++ } else {
++ if (vreg->speed_bin_corner_sum > 0)
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
++ prop_name, len,
++ tuple_size * vreg->corner_count * sizeof(u32),
++ tuple_size * vreg->speed_bin_corner_sum
++ * sizeof(u32),
++ tuple_size * vreg->fuse_combo_corner_sum
++ * sizeof(u32));
++ else
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
++ prop_name, len,
++ tuple_size * vreg->corner_count * sizeof(u32),
++ tuple_size * vreg->fuse_combo_corner_sum
++ * sizeof(u32));
++ return -EINVAL;
++ }
++
++ for (i = 0; i < tuple_size * vreg->corner_count; i++) {
++ rc = of_property_read_u32_index(node, prop_name, offset + i,
++ &out[i]);
++ if (rc) {
++ cpr3_err(vreg, "error reading property %s, rc=%d\n",
++ prop_name, rc);
++ return rc;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_parse_corner_band_array_property() - fill a per-corner band array
++ * from a portion of the values specified for a device tree
++ * property
++ * @vreg: Pointer to the CPR3 regulator
++ * @prop_name: The name of the device tree property to read from
++ * @tuple_size: The number of elements in each per-corner band tuple
++ * @out: Output data array which must be of size:
++ * tuple_size * vreg->corner_band_count
++ *
++ * cpr3_parse_common_corner_data() must be called for vreg before this function
++ * is called so that fuse combo and speed bin size elements are initialized.
++ * In addition, corner band fuse combo and speed bin sum and offset elements
++ * must be initialized prior to executing this function.
++ *
++ * Three formats are supported for the device tree property:
++ * 1. Length == tuple_size * vreg->corner_band_count
++ * (reading begins at index 0)
++ * 2. Length == tuple_size * vreg->fuse_combo_corner_band_sum
++ * (reading begins at index tuple_size *
++ * vreg->fuse_combo_corner_band_offset)
++ * 3. Length == tuple_size * vreg->speed_bin_corner_band_sum
++ * (reading begins at index tuple_size *
++ * vreg->speed_bin_corner_band_offset)
++ *
++ * All other property lengths are treated as errors.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_corner_band_array_property(struct cpr3_regulator *vreg,
++ const char *prop_name, int tuple_size, u32 *out)
++{
++ struct device_node *node = vreg->of_node;
++ int len = 0;
++ int i, offset, rc;
++
++ if (!of_find_property(node, prop_name, &len)) {
++ cpr3_err(vreg, "property %s is missing\n", prop_name);
++ return -EINVAL;
++ }
++
++ if (len == tuple_size * vreg->corner_band_count * sizeof(u32)) {
++ offset = 0;
++ } else if (len == tuple_size * vreg->fuse_combo_corner_band_sum
++ * sizeof(u32)) {
++ offset = tuple_size * vreg->fuse_combo_corner_band_offset;
++ } else if (vreg->speed_bin_corner_band_sum > 0 &&
++ len == tuple_size * vreg->speed_bin_corner_band_sum *
++ sizeof(u32)) {
++ offset = tuple_size * vreg->speed_bin_corner_band_offset;
++ } else {
++ if (vreg->speed_bin_corner_band_sum > 0)
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu, %zu, or %zu\n",
++ prop_name, len,
++ tuple_size * vreg->corner_band_count *
++ sizeof(u32),
++ tuple_size * vreg->speed_bin_corner_band_sum
++ * sizeof(u32),
++ tuple_size * vreg->fuse_combo_corner_band_sum
++ * sizeof(u32));
++ else
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
++ prop_name, len,
++ tuple_size * vreg->corner_band_count *
++ sizeof(u32),
++ tuple_size * vreg->fuse_combo_corner_band_sum
++ * sizeof(u32));
++ return -EINVAL;
++ }
++
++ for (i = 0; i < tuple_size * vreg->corner_band_count; i++) {
++ rc = of_property_read_u32_index(node, prop_name, offset + i,
++ &out[i]);
++ if (rc) {
++ cpr3_err(vreg, "error reading property %s, rc=%d\n",
++ prop_name, rc);
++ return rc;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_parse_common_corner_data() - parse common CPR3 properties relating to
++ * the corners supported by a CPR3 regulator from device tree
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function reads, validates, and utilizes the following device tree
++ * properties: qcom,cpr-fuse-corners, qcom,cpr-fuse-combos, qcom,cpr-speed-bins,
++ * qcom,cpr-speed-bin-corners, qcom,cpr-corners, qcom,cpr-voltage-ceiling,
++ * qcom,cpr-voltage-floor, qcom,corner-frequencies,
++ * and qcom,cpr-corner-fmax-map.
++ *
++ * It initializes these CPR3 regulator elements: corner, corner_count,
++ * fuse_combos_supported, fuse_corner_map, and speed_bins_supported. It
++ * initializes these elements for each corner: ceiling_volt, floor_volt,
++ * proc_freq, and cpr_fuse_corner.
++ *
++ * It requires that the following CPR3 regulator elements be initialized before
++ * being called: fuse_corner_count, fuse_combo, and speed_bin_fuse.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_common_corner_data(struct cpr3_regulator *vreg)
++{
++ struct device_node *node = vreg->of_node;
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ u32 max_fuse_combos, fuse_corners, aging_allowed = 0;
++ u32 max_speed_bins = 0;
++ u32 *combo_corners;
++ u32 *speed_bin_corners;
++ u32 *temp;
++ int i, j, rc;
++
++ rc = of_property_read_u32(node, "qcom,cpr-fuse-corners", &fuse_corners);
++ if (rc) {
++ cpr3_err(vreg, "error reading property qcom,cpr-fuse-corners, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (vreg->fuse_corner_count != fuse_corners) {
++ cpr3_err(vreg, "device tree config supports %d fuse corners but the hardware has %d fuse corners\n",
++ fuse_corners, vreg->fuse_corner_count);
++ return -EINVAL;
++ }
++
++ rc = of_property_read_u32(node, "qcom,cpr-fuse-combos",
++ &max_fuse_combos);
++ if (rc) {
++ cpr3_err(vreg, "error reading property qcom,cpr-fuse-combos, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ /*
++ * Sanity check against arbitrarily large value to avoid excessive
++ * memory allocation.
++ */
++ if (max_fuse_combos > 100 || max_fuse_combos == 0) {
++ cpr3_err(vreg, "qcom,cpr-fuse-combos is invalid: %u\n",
++ max_fuse_combos);
++ return -EINVAL;
++ }
++
++ if (vreg->fuse_combo >= max_fuse_combos) {
++ cpr3_err(vreg, "device tree config supports fuse combos 0-%u but the hardware has combo %d\n",
++ max_fuse_combos - 1, vreg->fuse_combo);
++ BUG_ON(1);
++ return -EINVAL;
++ }
++
++ vreg->fuse_combos_supported = max_fuse_combos;
++
++ of_property_read_u32(node, "qcom,cpr-speed-bins", &max_speed_bins);
++
++ /*
++ * Sanity check against arbitrarily large value to avoid excessive
++ * memory allocation.
++ */
++ if (max_speed_bins > 100) {
++ cpr3_err(vreg, "qcom,cpr-speed-bins is invalid: %u\n",
++ max_speed_bins);
++ return -EINVAL;
++ }
++
++ if (max_speed_bins && vreg->speed_bin_fuse >= max_speed_bins) {
++ cpr3_err(vreg, "device tree config supports speed bins 0-%u but the hardware has speed bin %d\n",
++ max_speed_bins - 1, vreg->speed_bin_fuse);
++ BUG();
++ return -EINVAL;
++ }
++
++ vreg->speed_bins_supported = max_speed_bins;
++
++ combo_corners = kcalloc(vreg->fuse_combos_supported,
++ sizeof(*combo_corners), GFP_KERNEL);
++ if (!combo_corners)
++ return -ENOMEM;
++
++ rc = of_property_read_u32_array(node, "qcom,cpr-corners", combo_corners,
++ vreg->fuse_combos_supported);
++ if (rc == -EOVERFLOW) {
++ /* Single value case */
++ rc = of_property_read_u32(node, "qcom,cpr-corners",
++ combo_corners);
++ for (i = 1; i < vreg->fuse_combos_supported; i++)
++ combo_corners[i] = combo_corners[0];
++ }
++ if (rc) {
++ cpr3_err(vreg, "error reading property qcom,cpr-corners, rc=%d\n",
++ rc);
++ kfree(combo_corners);
++ return rc;
++ }
++
++ vreg->fuse_combo_offset = 0;
++ vreg->fuse_combo_corner_sum = 0;
++ for (i = 0; i < vreg->fuse_combos_supported; i++) {
++ vreg->fuse_combo_corner_sum += combo_corners[i];
++ if (i < vreg->fuse_combo)
++ vreg->fuse_combo_offset += combo_corners[i];
++ }
++
++ vreg->corner_count = combo_corners[vreg->fuse_combo];
++
++ kfree(combo_corners);
++
++ vreg->speed_bin_offset = 0;
++ vreg->speed_bin_corner_sum = 0;
++ if (vreg->speed_bins_supported > 0) {
++ speed_bin_corners = kcalloc(vreg->speed_bins_supported,
++ sizeof(*speed_bin_corners), GFP_KERNEL);
++ if (!speed_bin_corners)
++ return -ENOMEM;
++
++ rc = of_property_read_u32_array(node,
++ "qcom,cpr-speed-bin-corners", speed_bin_corners,
++ vreg->speed_bins_supported);
++ if (rc) {
++ cpr3_err(vreg, "error reading property qcom,cpr-speed-bin-corners, rc=%d\n",
++ rc);
++ kfree(speed_bin_corners);
++ return rc;
++ }
++
++ for (i = 0; i < vreg->speed_bins_supported; i++) {
++ vreg->speed_bin_corner_sum += speed_bin_corners[i];
++ if (i < vreg->speed_bin_fuse)
++ vreg->speed_bin_offset += speed_bin_corners[i];
++ }
++
++ if (speed_bin_corners[vreg->speed_bin_fuse]
++ != vreg->corner_count) {
++ cpr3_err(vreg, "qcom,cpr-corners and qcom,cpr-speed-bin-corners conflict on number of corners: %d vs %u\n",
++ vreg->corner_count,
++ speed_bin_corners[vreg->speed_bin_fuse]);
++ kfree(speed_bin_corners);
++ return -EINVAL;
++ }
++
++ kfree(speed_bin_corners);
++ }
++
++ vreg->corner = devm_kcalloc(ctrl->dev, vreg->corner_count,
++ sizeof(*vreg->corner), GFP_KERNEL);
++ temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
++ if (!vreg->corner || !temp)
++ return -ENOMEM;
++
++ rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-voltage-ceiling",
++ 1, temp);
++ if (rc)
++ goto free_temp;
++ for (i = 0; i < vreg->corner_count; i++) {
++ vreg->corner[i].ceiling_volt
++ = CPR3_ROUND(temp[i], ctrl->step_volt);
++ vreg->corner[i].abs_ceiling_volt = vreg->corner[i].ceiling_volt;
++ }
++
++ rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-voltage-floor",
++ 1, temp);
++ if (rc)
++ goto free_temp;
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].floor_volt
++ = CPR3_ROUND(temp[i], ctrl->step_volt);
++
++ /* Validate ceiling and floor values */
++ for (i = 0; i < vreg->corner_count; i++) {
++ if (vreg->corner[i].floor_volt
++ > vreg->corner[i].ceiling_volt) {
++ cpr3_err(vreg, "CPR floor[%d]=%d > ceiling[%d]=%d uV\n",
++ i, vreg->corner[i].floor_volt,
++ i, vreg->corner[i].ceiling_volt);
++ rc = -EINVAL;
++ goto free_temp;
++ }
++ }
++
++ /* Load optional system-supply voltages */
++ if (of_find_property(vreg->of_node, "qcom,system-voltage", NULL)) {
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,system-voltage", 1, temp);
++ if (rc)
++ goto free_temp;
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].system_volt = temp[i];
++ }
++
++ rc = cpr3_parse_corner_array_property(vreg, "qcom,corner-frequencies",
++ 1, temp);
++ if (rc)
++ goto free_temp;
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].proc_freq = temp[i];
++
++ /* Validate frequencies */
++ for (i = 1; i < vreg->corner_count; i++) {
++ if (vreg->corner[i].proc_freq
++ < vreg->corner[i - 1].proc_freq) {
++ cpr3_err(vreg, "invalid frequency: freq[%d]=%u < freq[%d]=%u\n",
++ i, vreg->corner[i].proc_freq, i - 1,
++ vreg->corner[i - 1].proc_freq);
++ rc = -EINVAL;
++ goto free_temp;
++ }
++ }
++
++ vreg->fuse_corner_map = devm_kcalloc(ctrl->dev, vreg->fuse_corner_count,
++ sizeof(*vreg->fuse_corner_map), GFP_KERNEL);
++ if (!vreg->fuse_corner_map) {
++ rc = -ENOMEM;
++ goto free_temp;
++ }
++
++ rc = cpr3_parse_array_property(vreg, "qcom,cpr-corner-fmax-map",
++ vreg->fuse_corner_count, temp);
++ if (rc)
++ goto free_temp;
++ for (i = 0; i < vreg->fuse_corner_count; i++) {
++ vreg->fuse_corner_map[i] = temp[i] - CPR3_CORNER_OFFSET;
++ if (temp[i] < CPR3_CORNER_OFFSET
++ || temp[i] > vreg->corner_count + CPR3_CORNER_OFFSET) {
++ cpr3_err(vreg, "invalid corner value specified in qcom,cpr-corner-fmax-map: %u\n",
++ temp[i]);
++ rc = -EINVAL;
++ goto free_temp;
++ } else if (i > 0 && temp[i - 1] >= temp[i]) {
++ cpr3_err(vreg, "invalid corner %u less than or equal to previous corner %u\n",
++ temp[i], temp[i - 1]);
++ rc = -EINVAL;
++ goto free_temp;
++ }
++ }
++ if (temp[vreg->fuse_corner_count - 1] != vreg->corner_count)
++ cpr3_debug(vreg, "Note: highest Fmax corner %u in qcom,cpr-corner-fmax-map does not match highest supported corner %d\n",
++ temp[vreg->fuse_corner_count - 1],
++ vreg->corner_count);
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ for (j = 0; j < vreg->fuse_corner_count; j++) {
++ if (i + CPR3_CORNER_OFFSET <= temp[j]) {
++ vreg->corner[i].cpr_fuse_corner = j;
++ break;
++ }
++ }
++ if (j == vreg->fuse_corner_count) {
++ /*
++ * Handle the case where the highest fuse corner maps
++ * to a corner below the highest corner.
++ */
++ vreg->corner[i].cpr_fuse_corner
++ = vreg->fuse_corner_count - 1;
++ }
++ }
++
++ if (of_find_property(vreg->of_node,
++ "qcom,allow-aging-voltage-adjustment", NULL)) {
++ rc = cpr3_parse_array_property(vreg,
++ "qcom,allow-aging-voltage-adjustment",
++ 1, &aging_allowed);
++ if (rc)
++ goto free_temp;
++
++ vreg->aging_allowed = aging_allowed;
++ }
++
++ if (of_find_property(vreg->of_node,
++ "qcom,allow-aging-open-loop-voltage-adjustment", NULL)) {
++ rc = cpr3_parse_array_property(vreg,
++ "qcom,allow-aging-open-loop-voltage-adjustment",
++ 1, &aging_allowed);
++ if (rc)
++ goto free_temp;
++
++ vreg->aging_allow_open_loop_adj = aging_allowed;
++ }
++
++ if (vreg->aging_allowed) {
++ if (ctrl->aging_ref_volt <= 0) {
++ cpr3_err(ctrl, "qcom,cpr-aging-ref-voltage must be specified\n");
++ rc = -EINVAL;
++ goto free_temp;
++ }
++
++ rc = cpr3_parse_array_property(vreg,
++ "qcom,cpr-aging-max-voltage-adjustment",
++ 1, &vreg->aging_max_adjust_volt);
++ if (rc)
++ goto free_temp;
++
++ rc = cpr3_parse_array_property(vreg,
++ "qcom,cpr-aging-ref-corner", 1, &vreg->aging_corner);
++ if (rc) {
++ goto free_temp;
++ } else if (vreg->aging_corner < CPR3_CORNER_OFFSET
++ || vreg->aging_corner > vreg->corner_count - 1
++ + CPR3_CORNER_OFFSET) {
++ cpr3_err(vreg, "aging reference corner=%d not in range [%d, %d]\n",
++ vreg->aging_corner, CPR3_CORNER_OFFSET,
++ vreg->corner_count - 1 + CPR3_CORNER_OFFSET);
++ rc = -EINVAL;
++ goto free_temp;
++ }
++ vreg->aging_corner -= CPR3_CORNER_OFFSET;
++
++ if (of_find_property(vreg->of_node, "qcom,cpr-aging-derate",
++ NULL)) {
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,cpr-aging-derate", 1, temp);
++ if (rc)
++ goto free_temp;
++
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].aging_derate = temp[i];
++ } else {
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].aging_derate
++ = CPR3_AGING_DERATE_UNITY;
++ }
++ }
++
++free_temp:
++ kfree(temp);
++ return rc;
++}
++
++/**
++ * cpr3_parse_thread_u32() - parse the specified property from the CPR3 thread's
++ * device tree node and verify that it is within the allowed limits
++ * @thread: Pointer to the CPR3 thread
++ * @propname: The name of the device tree property to read
++ * @out_value: The output pointer to fill with the value read
++ * @value_min: The minimum allowed property value
++ * @value_max: The maximum allowed property value
++ *
++ * This function prints a verbose error message if the property is missing or
++ * has a value which is not within the specified range.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_thread_u32(struct cpr3_thread *thread, const char *propname,
++ u32 *out_value, u32 value_min, u32 value_max)
++{
++ int rc;
++
++ rc = of_property_read_u32(thread->of_node, propname, out_value);
++ if (rc) {
++ cpr3_err(thread->ctrl, "thread %u error reading property %s, rc=%d\n",
++ thread->thread_id, propname, rc);
++ return rc;
++ }
++
++ if (*out_value < value_min || *out_value > value_max) {
++ cpr3_err(thread->ctrl, "thread %u %s=%u is invalid; allowed range: [%u, %u]\n",
++ thread->thread_id, propname, *out_value, value_min,
++ value_max);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_parse_ctrl_u32() - parse the specified property from the CPR3
++ * controller's device tree node and verify that it is within the
++ * allowed limits
++ * @ctrl: Pointer to the CPR3 controller
++ * @propname: The name of the device tree property to read
++ * @out_value: The output pointer to fill with the value read
++ * @value_min: The minimum allowed property value
++ * @value_max: The maximum allowed property value
++ *
++ * This function prints a verbose error message if the property is missing or
++ * has a value which is not within the specified range.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_ctrl_u32(struct cpr3_controller *ctrl, const char *propname,
++ u32 *out_value, u32 value_min, u32 value_max)
++{
++ int rc;
++
++ rc = of_property_read_u32(ctrl->dev->of_node, propname, out_value);
++ if (rc) {
++ cpr3_err(ctrl, "error reading property %s, rc=%d\n",
++ propname, rc);
++ return rc;
++ }
++
++ if (*out_value < value_min || *out_value > value_max) {
++ cpr3_err(ctrl, "%s=%u is invalid; allowed range: [%u, %u]\n",
++ propname, *out_value, value_min, value_max);
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_parse_common_thread_data() - parse common CPR3 thread properties from
++ * device tree
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_common_thread_data(struct cpr3_thread *thread)
++{
++ int rc;
++
++ rc = cpr3_parse_thread_u32(thread, "qcom,cpr-consecutive-up",
++ &thread->consecutive_up, CPR3_CONSECUTIVE_UP_DOWN_MIN,
++ CPR3_CONSECUTIVE_UP_DOWN_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_thread_u32(thread, "qcom,cpr-consecutive-down",
++ &thread->consecutive_down, CPR3_CONSECUTIVE_UP_DOWN_MIN,
++ CPR3_CONSECUTIVE_UP_DOWN_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_thread_u32(thread, "qcom,cpr-up-threshold",
++ &thread->up_threshold, CPR3_UP_DOWN_THRESHOLD_MIN,
++ CPR3_UP_DOWN_THRESHOLD_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_thread_u32(thread, "qcom,cpr-down-threshold",
++ &thread->down_threshold, CPR3_UP_DOWN_THRESHOLD_MIN,
++ CPR3_UP_DOWN_THRESHOLD_MAX);
++ if (rc)
++ return rc;
++
++ return rc;
++}
++
++/**
++ * cpr3_parse_irq_affinity() - parse CPR IRQ affinity information
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_parse_irq_affinity(struct cpr3_controller *ctrl)
++{
++ struct device_node *cpu_node;
++ int i, cpu;
++ int len = 0;
++
++ if (!of_find_property(ctrl->dev->of_node, "qcom,cpr-interrupt-affinity",
++ &len)) {
++ /* No IRQ affinity required */
++ return 0;
++ }
++
++ len /= sizeof(u32);
++
++ for (i = 0; i < len; i++) {
++ cpu_node = of_parse_phandle(ctrl->dev->of_node,
++ "qcom,cpr-interrupt-affinity", i);
++ if (!cpu_node) {
++ cpr3_err(ctrl, "could not find CPU node %d\n", i);
++ return -EINVAL;
++ }
++
++ for_each_possible_cpu(cpu) {
++ if (of_get_cpu_node(cpu, NULL) == cpu_node) {
++ cpumask_set_cpu(cpu, &ctrl->irq_affinity_mask);
++ break;
++ }
++ }
++ of_node_put(cpu_node);
++ }
++
++ return 0;
++}
++
++static int cpr3_panic_notifier_init(struct cpr3_controller *ctrl)
++{
++ struct device_node *node = ctrl->dev->of_node;
++ struct cpr3_panic_regs_info *panic_regs_info;
++ struct cpr3_reg_info *regs;
++ int i, reg_count, len, rc = 0;
++
++ if (!of_find_property(node, "qcom,cpr-panic-reg-addr-list", &len)) {
++ /* panic register address list not specified */
++ return rc;
++ }
++
++ reg_count = len / sizeof(u32);
++ if (!reg_count) {
++ cpr3_err(ctrl, "qcom,cpr-panic-reg-addr-list has invalid len = %d\n",
++ len);
++ return -EINVAL;
++ }
++
++ if (!of_find_property(node, "qcom,cpr-panic-reg-name-list", NULL)) {
++ cpr3_err(ctrl, "property qcom,cpr-panic-reg-name-list not specified\n");
++ return -EINVAL;
++ }
++
++ len = of_property_count_strings(node, "qcom,cpr-panic-reg-name-list");
++ if (reg_count != len) {
++ cpr3_err(ctrl, "qcom,cpr-panic-reg-name-list should have %d strings\n",
++ reg_count);
++ return -EINVAL;
++ }
++
++ panic_regs_info = devm_kzalloc(ctrl->dev, sizeof(*panic_regs_info),
++ GFP_KERNEL);
++ if (!panic_regs_info)
++ return -ENOMEM;
++
++ regs = devm_kcalloc(ctrl->dev, reg_count, sizeof(*regs), GFP_KERNEL);
++ if (!regs)
++ return -ENOMEM;
++
++ for (i = 0; i < reg_count; i++) {
++ rc = of_property_read_string_index(node,
++ "qcom,cpr-panic-reg-name-list", i,
++ &(regs[i].name));
++ if (rc) {
++ cpr3_err(ctrl, "error reading property qcom,cpr-panic-reg-name-list, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = of_property_read_u32_index(node,
++ "qcom,cpr-panic-reg-addr-list", i,
++ &(regs[i].addr));
++ if (rc) {
++ cpr3_err(ctrl, "error reading property qcom,cpr-panic-reg-addr-list, rc=%d\n",
++ rc);
++ return rc;
++ }
++ regs[i].virt_addr = devm_ioremap(ctrl->dev, regs[i].addr, 0x4);
++ if (!regs[i].virt_addr) {
++ pr_err("Unable to map panic register addr 0x%08x\n",
++ regs[i].addr);
++ return -EINVAL;
++ }
++ regs[i].value = 0xFFFFFFFF;
++ }
++
++ panic_regs_info->reg_count = reg_count;
++ panic_regs_info->regs = regs;
++ ctrl->panic_regs_info = panic_regs_info;
++
++ return rc;
++}
++
++/**
++ * cpr3_parse_common_ctrl_data() - parse common CPR3 controller properties from
++ * device tree
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_common_ctrl_data(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-sensor-time",
++ &ctrl->sensor_time, 0, UINT_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-loop-time",
++ &ctrl->loop_time, 0, UINT_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-idle-cycles",
++ &ctrl->idle_clocks, CPR3_IDLE_CLOCKS_MIN,
++ CPR3_IDLE_CLOCKS_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-step-quot-init-min",
++ &ctrl->step_quot_init_min, CPR3_STEP_QUOT_MIN,
++ CPR3_STEP_QUOT_MAX);
++ if (rc)
++ return rc;
++
++ rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-step-quot-init-max",
++ &ctrl->step_quot_init_max, CPR3_STEP_QUOT_MIN,
++ CPR3_STEP_QUOT_MAX);
++ if (rc)
++ return rc;
++
++ rc = of_property_read_u32(ctrl->dev->of_node, "qcom,voltage-step",
++ &ctrl->step_volt);
++ if (rc) {
++ cpr3_err(ctrl, "error reading property qcom,voltage-step, rc=%d\n",
++ rc);
++ return rc;
++ }
++ if (ctrl->step_volt <= 0) {
++ cpr3_err(ctrl, "qcom,voltage-step=%d is invalid\n",
++ ctrl->step_volt);
++ return -EINVAL;
++ }
++
++ rc = cpr3_parse_ctrl_u32(ctrl, "qcom,cpr-count-mode",
++ &ctrl->count_mode, CPR3_COUNT_MODE_ALL_AT_ONCE_MIN,
++ CPR3_COUNT_MODE_STAGGERED);
++ if (rc)
++ return rc;
++
++ /* Count repeat is optional */
++ ctrl->count_repeat = 0;
++ of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-count-repeat",
++ &ctrl->count_repeat);
++
++ ctrl->cpr_allowed_sw =
++ of_property_read_bool(ctrl->dev->of_node, "qcom,cpr-enable") ||
++ ctrl->cpr_global_setting == CPR_CLOSED_LOOP_EN;
++
++ rc = cpr3_parse_irq_affinity(ctrl);
++ if (rc)
++ return rc;
++
++ /* Aging reference voltage is optional */
++ ctrl->aging_ref_volt = 0;
++ of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-aging-ref-voltage",
++ &ctrl->aging_ref_volt);
++
++ /* Aging possible bitmask is optional */
++ ctrl->aging_possible_mask = 0;
++ of_property_read_u32(ctrl->dev->of_node,
++ "qcom,cpr-aging-allowed-reg-mask",
++ &ctrl->aging_possible_mask);
++
++ if (ctrl->aging_possible_mask) {
++ /*
++ * Aging possible register value required if bitmask is
++ * specified
++ */
++ rc = cpr3_parse_ctrl_u32(ctrl,
++ "qcom,cpr-aging-allowed-reg-value",
++ &ctrl->aging_possible_val, 0, UINT_MAX);
++ if (rc)
++ return rc;
++ }
++
++ if (of_find_property(ctrl->dev->of_node, "clock-names", NULL)) {
++ ctrl->core_clk = devm_clk_get(ctrl->dev, "core_clk");
++ if (IS_ERR(ctrl->core_clk)) {
++ rc = PTR_ERR(ctrl->core_clk);
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable request core clock, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ rc = cpr3_panic_notifier_init(ctrl);
++ if (rc)
++ return rc;
++
++ if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) {
++ ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
++ if (IS_ERR(ctrl->vdd_regulator)) {
++ rc = PTR_ERR(ctrl->vdd_regulator);
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
++ rc);
++ return rc;
++ }
++ } else {
++ cpr3_err(ctrl, "vdd supply is not defined\n");
++ return -ENODEV;
++ }
++
++ ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
++ "system");
++ if (IS_ERR(ctrl->system_regulator)) {
++ rc = PTR_ERR(ctrl->system_regulator);
++ if (rc != -EPROBE_DEFER) {
++ rc = 0;
++ ctrl->system_regulator = NULL;
++ } else {
++ return rc;
++ }
++ }
++
++ ctrl->mem_acc_regulator = devm_regulator_get_optional(ctrl->dev,
++ "mem-acc");
++ if (IS_ERR(ctrl->mem_acc_regulator)) {
++ rc = PTR_ERR(ctrl->mem_acc_regulator);
++ if (rc != -EPROBE_DEFER) {
++ rc = 0;
++ ctrl->mem_acc_regulator = NULL;
++ } else {
++ return rc;
++ }
++ }
++
++ return rc;
++}
++
++/**
++ * cpr3_parse_open_loop_common_ctrl_data() - parse common open loop CPR3
++ * controller properties from device tree
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_open_loop_common_ctrl_data(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ rc = of_property_read_u32(ctrl->dev->of_node, "qcom,voltage-step",
++ &ctrl->step_volt);
++ if (rc) {
++ cpr3_err(ctrl, "error reading property qcom,voltage-step, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (ctrl->step_volt <= 0) {
++ cpr3_err(ctrl, "qcom,voltage-step=%d is invalid\n",
++ ctrl->step_volt);
++ return -EINVAL;
++ }
++
++ if (of_find_property(ctrl->dev->of_node, "vdd-supply", NULL)) {
++ ctrl->vdd_regulator = devm_regulator_get(ctrl->dev, "vdd");
++ if (IS_ERR(ctrl->vdd_regulator)) {
++ rc = PTR_ERR(ctrl->vdd_regulator);
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable to request vdd regulator, rc=%d\n",
++ rc);
++ return rc;
++ }
++ } else {
++ cpr3_err(ctrl, "vdd supply is not defined\n");
++ return -ENODEV;
++ }
++
++ ctrl->system_regulator = devm_regulator_get_optional(ctrl->dev,
++ "system");
++ if (IS_ERR(ctrl->system_regulator)) {
++ rc = PTR_ERR(ctrl->system_regulator);
++ if (rc != -EPROBE_DEFER) {
++ rc = 0;
++ ctrl->system_regulator = NULL;
++ } else {
++ return rc;
++ }
++ } else {
++ rc = regulator_enable(ctrl->system_regulator);
++ }
++
++ ctrl->mem_acc_regulator = devm_regulator_get_optional(ctrl->dev,
++ "mem-acc");
++ if (IS_ERR(ctrl->mem_acc_regulator)) {
++ rc = PTR_ERR(ctrl->mem_acc_regulator);
++ if (rc != -EPROBE_DEFER) {
++ rc = 0;
++ ctrl->mem_acc_regulator = NULL;
++ } else {
++ return rc;
++ }
++ }
++
++ return rc;
++}
++
++/**
++ * cpr3_limit_open_loop_voltages() - modify the open-loop voltage of each corner
++ * so that it fits within the floor to ceiling
++ * voltage range of the corner
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function clips the open-loop voltage for each corner so that it is
++ * limited to the floor to ceiling range. It also rounds each open-loop voltage
++ * so that it corresponds to a set point available to the underlying regulator.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_limit_open_loop_voltages(struct cpr3_regulator *vreg)
++{
++ int i, volt;
++
++ cpr3_debug(vreg, "open-loop voltages after trimming and rounding:\n");
++ for (i = 0; i < vreg->corner_count; i++) {
++ volt = CPR3_ROUND(vreg->corner[i].open_loop_volt,
++ vreg->thread->ctrl->step_volt);
++ if (volt < vreg->corner[i].floor_volt)
++ volt = vreg->corner[i].floor_volt;
++ else if (volt > vreg->corner[i].ceiling_volt)
++ volt = vreg->corner[i].ceiling_volt;
++ vreg->corner[i].open_loop_volt = volt;
++ cpr3_debug(vreg, "corner[%2d]: open-loop=%d uV\n", i, volt);
++ }
++
++ return 0;
++}
++
++/**
++ * cpr3_open_loop_voltage_as_ceiling() - configures the ceiling voltage for each
++ * corner to equal the open-loop voltage if the relevant device
++ * tree property is found for the CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function assumes that the the open-loop voltage for each corner has
++ * already been rounded to the nearest allowed set point and that it falls
++ * within the floor to ceiling range.
++ *
++ * Return: none
++ */
++void cpr3_open_loop_voltage_as_ceiling(struct cpr3_regulator *vreg)
++{
++ int i;
++
++ if (!of_property_read_bool(vreg->of_node,
++ "qcom,cpr-scaled-open-loop-voltage-as-ceiling"))
++ return;
++
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].ceiling_volt
++ = vreg->corner[i].open_loop_volt;
++}
++
++/**
++ * cpr3_limit_floor_voltages() - raise the floor voltage of each corner so that
++ * the optional maximum floor to ceiling voltage range specified in
++ * device tree is satisfied
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function also ensures that the open-loop voltage for each corner falls
++ * within the final floor to ceiling voltage range and that floor voltages
++ * increase monotonically.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
++{
++ char *prop = "qcom,cpr-floor-to-ceiling-max-range";
++ int i, floor_new;
++ u32 *floor_range;
++ int rc = 0;
++
++ if (!of_find_property(vreg->of_node, prop, NULL))
++ goto enforce_monotonicity;
++
++ floor_range = kcalloc(vreg->corner_count, sizeof(*floor_range),
++ GFP_KERNEL);
++ if (!floor_range)
++ return -ENOMEM;
++
++ rc = cpr3_parse_corner_array_property(vreg, prop, 1, floor_range);
++ if (rc)
++ goto free_floor_adjust;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ if ((s32)floor_range[i] >= 0) {
++ floor_new = CPR3_ROUND(vreg->corner[i].ceiling_volt
++ - floor_range[i],
++ vreg->thread->ctrl->step_volt);
++
++ vreg->corner[i].floor_volt = max(floor_new,
++ vreg->corner[i].floor_volt);
++ if (vreg->corner[i].open_loop_volt
++ < vreg->corner[i].floor_volt)
++ vreg->corner[i].open_loop_volt
++ = vreg->corner[i].floor_volt;
++ }
++ }
++
++free_floor_adjust:
++ kfree(floor_range);
++
++enforce_monotonicity:
++ /* Ensure that floor voltages increase monotonically. */
++ for (i = 1; i < vreg->corner_count; i++) {
++ if (vreg->corner[i].floor_volt
++ < vreg->corner[i - 1].floor_volt) {
++ cpr3_debug(vreg, "corner %d floor voltage=%d uV < corner %d voltage=%d uV; overriding: corner %d voltage=%d\n",
++ i, vreg->corner[i].floor_volt,
++ i - 1, vreg->corner[i - 1].floor_volt,
++ i, vreg->corner[i - 1].floor_volt);
++ vreg->corner[i].floor_volt
++ = vreg->corner[i - 1].floor_volt;
++
++ if (vreg->corner[i].open_loop_volt
++ < vreg->corner[i].floor_volt)
++ vreg->corner[i].open_loop_volt
++ = vreg->corner[i].floor_volt;
++ if (vreg->corner[i].ceiling_volt
++ < vreg->corner[i].floor_volt)
++ vreg->corner[i].ceiling_volt
++ = vreg->corner[i].floor_volt;
++ }
++ }
++
++ return rc;
++}
++
++/**
++ * cpr3_print_quots() - print CPR target quotients into the kernel log for
++ * debugging purposes
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: none
++ */
++void cpr3_print_quots(struct cpr3_regulator *vreg)
++{
++ int i, j, pos;
++ size_t buflen;
++ char *buf;
++
++ buflen = sizeof(*buf) * CPR3_RO_COUNT * (MAX_CHARS_PER_INT + 2);
++ buf = kzalloc(buflen, GFP_KERNEL);
++ if (!buf)
++ return;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ for (j = 0, pos = 0; j < CPR3_RO_COUNT; j++)
++ pos += scnprintf(buf + pos, buflen - pos, " %u",
++ vreg->corner[i].target_quot[j]);
++ cpr3_debug(vreg, "target quots[%2d]:%s\n", i, buf);
++ }
++
++ kfree(buf);
++}
++
++/**
++ * cpr3_determine_part_type() - determine the part type (SS/TT/FF).
++ *
++ * qcom,cpr-part-types prop tells the number of part types for which correction
++ * voltages are different. Another prop qcom,cpr-parts-voltage will contain the
++ * open loop fuse voltage which will be compared with this part voltage
++ * and accordingly part type will de determined.
++ *
++ * if qcom,cpr-part-types has value n, then qcom,cpr-parts-voltage will be
++ * array of n - 1 elements which will contain the voltage in increasing order.
++ * This function compares the fused volatge with all these voltage and returns
++ * the first index for which the fused volatge is greater.
++ *
++ * @vreg: Pointer to the CPR3 regulator
++ * @fuse_volt: fused open loop voltage which will be compared with
++ * qcom,cpr-parts-voltage array
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_determine_part_type(struct cpr3_regulator *vreg, int fuse_volt)
++{
++ int i, rc, len;
++ u32 volt;
++ int soc_version_major;
++ char prop_name[100];
++ const char prop_name_def[] = "qcom,cpr-parts-voltage";
++ const char prop_name_v2[] = "qcom,cpr-parts-voltage-v2";
++
++ soc_version_major = read_ipq_soc_version_major();
++ BUG_ON(soc_version_major <= 0);
++
++ if (of_property_read_u32(vreg->of_node, "qcom,cpr-part-types",
++ &vreg->part_type_supported))
++ return 0;
++
++ if (soc_version_major > 1)
++ strlcpy(prop_name, prop_name_v2, sizeof(prop_name_v2));
++ else
++ strlcpy(prop_name, prop_name_def, sizeof(prop_name_def));
++
++ if (!of_find_property(vreg->of_node, prop_name, &len)) {
++ cpr3_err(vreg, "property %s is missing\n", prop_name);
++ return -EINVAL;
++ }
++
++ if (len != (vreg->part_type_supported - 1) * sizeof(u32)) {
++ cpr3_err(vreg, "wrong len in qcom,cpr-parts-voltage\n");
++ return -EINVAL;
++ }
++
++ for (i = 0; i < vreg->part_type_supported - 1; i++) {
++ rc = of_property_read_u32_index(vreg->of_node,
++ prop_name, i, &volt);
++ if (rc) {
++ cpr3_err(vreg, "error reading property %s, rc=%d\n",
++ prop_name, rc);
++ return rc;
++ }
++
++ if (fuse_volt < volt)
++ break;
++ }
++
++ vreg->part_type = i;
++ return 0;
++}
++
++int cpr3_determine_temp_base_open_loop_correction(struct cpr3_regulator *vreg,
++ int *fuse_volt)
++{
++ int i, rc, prev_volt;
++ int *volt_adjust;
++ char prop_str[75];
++ int soc_version_major = read_ipq_soc_version_major();
++
++ BUG_ON(soc_version_major <= 0);
++
++ if (vreg->part_type_supported) {
++ if (soc_version_major > 1)
++ snprintf(prop_str, sizeof(prop_str),
++ "qcom,cpr-cold-temp-voltage-adjustment-v2-%d",
++ vreg->part_type);
++ else
++ snprintf(prop_str, sizeof(prop_str),
++ "qcom,cpr-cold-temp-voltage-adjustment-%d",
++ vreg->part_type);
++ } else {
++ strlcpy(prop_str, "qcom,cpr-cold-temp-voltage-adjustment",
++ sizeof(prop_str));
++ }
++
++ if (!of_find_property(vreg->of_node, prop_str, NULL)) {
++ /* No adjustment required. */
++ cpr3_info(vreg, "No cold temperature adjustment required.\n");
++ return 0;
++ }
++
++ volt_adjust = kcalloc(vreg->fuse_corner_count, sizeof(*volt_adjust),
++ GFP_KERNEL);
++ if (!volt_adjust)
++ return -ENOMEM;
++
++ rc = cpr3_parse_array_property(vreg, prop_str,
++ vreg->fuse_corner_count, volt_adjust);
++ if (rc) {
++ cpr3_err(vreg, "could not load cold temp voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ for (i = 0; i < vreg->fuse_corner_count; i++) {
++ if (volt_adjust[i]) {
++ prev_volt = fuse_volt[i];
++ fuse_volt[i] += volt_adjust[i];
++ cpr3_debug(vreg,
++ "adjusted fuse corner %d open-loop voltage: %d -> %d uV\n",
++ i, prev_volt, fuse_volt[i]);
++ }
++ }
++
++done:
++ kfree(volt_adjust);
++ return rc;
++}
++
++/**
++ * cpr3_can_adjust_cold_temp() - Is cold temperature adjustment available
++ *
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function checks the cold temperature threshold is available
++ *
++ * Return: true on cold temperature threshold is available, else false
++ */
++bool cpr3_can_adjust_cold_temp(struct cpr3_regulator *vreg)
++{
++ char prop_str[75];
++ int soc_version_major = read_ipq_soc_version_major();
++
++ BUG_ON(soc_version_major <= 0);
++
++ if (soc_version_major > 1)
++ strlcpy(prop_str, "qcom,cpr-cold-temp-threshold-v2",
++ sizeof(prop_str));
++ else
++ strlcpy(prop_str, "qcom,cpr-cold-temp-threshold",
++ sizeof(prop_str));
++
++ if (!of_find_property(vreg->of_node, prop_str, NULL)) {
++ /* No adjustment required. */
++ return false;
++ } else
++ return true;
++}
++
++/**
++ * cpr3_get_cold_temp_threshold() - get cold temperature threshold
++ *
++ * @vreg: Pointer to the CPR3 regulator
++ * @cold_temp: cold temperature read.
++ *
++ * This function reads the cold temperature threshold below which
++ * cold temperature adjustment margins will be applied.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_get_cold_temp_threshold(struct cpr3_regulator *vreg, int *cold_temp)
++{
++ int rc;
++ u32 temp;
++ char req_prop_str[75], prop_str[75];
++ int soc_version_major = read_ipq_soc_version_major();
++
++ BUG_ON(soc_version_major <= 0);
++
++ if (vreg->part_type_supported) {
++ if (soc_version_major > 1)
++ snprintf(req_prop_str, sizeof(req_prop_str),
++ "qcom,cpr-cold-temp-voltage-adjustment-v2-%d",
++ vreg->part_type);
++ else
++ snprintf(req_prop_str, sizeof(req_prop_str),
++ "qcom,cpr-cold-temp-voltage-adjustment-%d",
++ vreg->part_type);
++ } else {
++ strlcpy(req_prop_str, "qcom,cpr-cold-temp-voltage-adjustment",
++ sizeof(req_prop_str));
++ }
++
++ if (soc_version_major > 1)
++ strlcpy(prop_str, "qcom,cpr-cold-temp-threshold-v2",
++ sizeof(prop_str));
++ else
++ strlcpy(prop_str, "qcom,cpr-cold-temp-threshold",
++ sizeof(prop_str));
++
++ if (!of_find_property(vreg->of_node, req_prop_str, NULL)) {
++ /* No adjustment required. */
++ cpr3_info(vreg, "Cold temperature adjustment not required.\n");
++ return 0;
++ }
++
++ if (!of_find_property(vreg->of_node, prop_str, NULL)) {
++ /* No adjustment required. */
++ cpr3_err(vreg, "Missing %s required for %s\n",
++ prop_str, req_prop_str);
++ return -EINVAL;
++ }
++
++ rc = of_property_read_u32(vreg->of_node, prop_str, &temp);
++ if (rc) {
++ cpr3_err(vreg, "error reading property %s, rc=%d\n",
++ prop_str, rc);
++ return rc;
++ }
++
++ *cold_temp = temp;
++ return 0;
++}
++
++/**
++ * cpr3_adjust_fused_open_loop_voltages() - adjust the fused open-loop voltages
++ * for each fuse corner according to device tree values
++ * @vreg: Pointer to the CPR3 regulator
++ * @fuse_volt: Pointer to an array of the fused open-loop voltage
++ * values
++ *
++ * Voltage values in fuse_volt are modified in place.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_adjust_fused_open_loop_voltages(struct cpr3_regulator *vreg,
++ int *fuse_volt)
++{
++ int i, rc, prev_volt;
++ int *volt_adjust;
++ char prop_str[75];
++ int soc_version_major = read_ipq_soc_version_major();
++
++ BUG_ON(soc_version_major <= 0);
++
++ if (vreg->part_type_supported) {
++ if (soc_version_major > 1)
++ snprintf(prop_str, sizeof(prop_str),
++ "qcom,cpr-open-loop-voltage-fuse-adjustment-v2-%d",
++ vreg->part_type);
++ else
++ snprintf(prop_str, sizeof(prop_str),
++ "qcom,cpr-open-loop-voltage-fuse-adjustment-%d",
++ vreg->part_type);
++ } else {
++ strlcpy(prop_str, "qcom,cpr-open-loop-voltage-fuse-adjustment",
++ sizeof(prop_str));
++ }
++
++ if (!of_find_property(vreg->of_node, prop_str, NULL)) {
++ /* No adjustment required. */
++ return 0;
++ }
++
++ volt_adjust = kcalloc(vreg->fuse_corner_count, sizeof(*volt_adjust),
++ GFP_KERNEL);
++ if (!volt_adjust)
++ return -ENOMEM;
++
++ rc = cpr3_parse_array_property(vreg,
++ prop_str, vreg->fuse_corner_count, volt_adjust);
++ if (rc) {
++ cpr3_err(vreg, "could not load open-loop fused voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ for (i = 0; i < vreg->fuse_corner_count; i++) {
++ if (volt_adjust[i]) {
++ prev_volt = fuse_volt[i];
++ fuse_volt[i] += volt_adjust[i];
++ cpr3_debug(vreg, "adjusted fuse corner %d open-loop voltage: %d --> %d uV\n",
++ i, prev_volt, fuse_volt[i]);
++ }
++ }
++
++done:
++ kfree(volt_adjust);
++ return rc;
++}
++
++/**
++ * cpr3_adjust_open_loop_voltages() - adjust the open-loop voltages for each
++ * corner according to device tree values
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_adjust_open_loop_voltages(struct cpr3_regulator *vreg)
++{
++ int i, rc, prev_volt, min_volt;
++ int *volt_adjust, *volt_diff;
++
++ if (!of_find_property(vreg->of_node,
++ "qcom,cpr-open-loop-voltage-adjustment", NULL)) {
++ /* No adjustment required. */
++ return 0;
++ }
++
++ volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
++ GFP_KERNEL);
++ volt_diff = kcalloc(vreg->corner_count, sizeof(*volt_diff), GFP_KERNEL);
++ if (!volt_adjust || !volt_diff) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,cpr-open-loop-voltage-adjustment", 1, volt_adjust);
++ if (rc) {
++ cpr3_err(vreg, "could not load open-loop voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ if (volt_adjust[i]) {
++ prev_volt = vreg->corner[i].open_loop_volt;
++ vreg->corner[i].open_loop_volt += volt_adjust[i];
++ cpr3_debug(vreg, "adjusted corner %d open-loop voltage: %d --> %d uV\n",
++ i, prev_volt, vreg->corner[i].open_loop_volt);
++ }
++ }
++
++ if (of_find_property(vreg->of_node,
++ "qcom,cpr-open-loop-voltage-min-diff", NULL)) {
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,cpr-open-loop-voltage-min-diff", 1, volt_diff);
++ if (rc) {
++ cpr3_err(vreg, "could not load minimum open-loop voltage differences, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ /*
++ * Ensure that open-loop voltages increase monotonically with respect
++ * to configurable minimum allowed differences.
++ */
++ for (i = 1; i < vreg->corner_count; i++) {
++ min_volt = vreg->corner[i - 1].open_loop_volt + volt_diff[i];
++ if (vreg->corner[i].open_loop_volt < min_volt) {
++ cpr3_debug(vreg, "adjusted corner %d open-loop voltage=%d uV < corner %d voltage=%d uV + min diff=%d uV; overriding: corner %d voltage=%d\n",
++ i, vreg->corner[i].open_loop_volt,
++ i - 1, vreg->corner[i - 1].open_loop_volt,
++ volt_diff[i], i, min_volt);
++ vreg->corner[i].open_loop_volt = min_volt;
++ }
++ }
++
++done:
++ kfree(volt_diff);
++ kfree(volt_adjust);
++ return rc;
++}
++
++/**
++ * cpr3_quot_adjustment() - returns the quotient adjustment value resulting from
++ * the specified voltage adjustment and RO scaling factor
++ * @ro_scale: The CPR ring oscillator (RO) scaling factor with units
++ * of QUOT/V
++ * @volt_adjust: The amount to adjust the voltage by in units of
++ * microvolts. This value may be positive or negative.
++ */
++int cpr3_quot_adjustment(int ro_scale, int volt_adjust)
++{
++ unsigned long long temp;
++ int quot_adjust;
++ int sign = 1;
++
++ if (ro_scale < 0) {
++ sign = -sign;
++ ro_scale = -ro_scale;
++ }
++
++ if (volt_adjust < 0) {
++ sign = -sign;
++ volt_adjust = -volt_adjust;
++ }
++
++ temp = (unsigned long long)ro_scale * (unsigned long long)volt_adjust;
++ do_div(temp, 1000000);
++
++ quot_adjust = temp;
++ quot_adjust *= sign;
++
++ return quot_adjust;
++}
++
++/**
++ * cpr3_voltage_adjustment() - returns the voltage adjustment value resulting
++ * from the specified quotient adjustment and RO scaling factor
++ * @ro_scale: The CPR ring oscillator (RO) scaling factor with units
++ * of QUOT/V
++ * @quot_adjust: The amount to adjust the quotient by in units of
++ * QUOT. This value may be positive or negative.
++ */
++int cpr3_voltage_adjustment(int ro_scale, int quot_adjust)
++{
++ unsigned long long temp;
++ int volt_adjust;
++ int sign = 1;
++
++ if (ro_scale < 0) {
++ sign = -sign;
++ ro_scale = -ro_scale;
++ }
++
++ if (quot_adjust < 0) {
++ sign = -sign;
++ quot_adjust = -quot_adjust;
++ }
++
++ if (ro_scale == 0)
++ return 0;
++
++ temp = (unsigned long long)quot_adjust * 1000000;
++ do_div(temp, ro_scale);
++
++ volt_adjust = temp;
++ volt_adjust *= sign;
++
++ return volt_adjust;
++}
++
++/**
++ * cpr3_parse_closed_loop_voltage_adjustments() - load per-fuse-corner and
++ * per-corner closed-loop adjustment values from device tree
++ * @vreg: Pointer to the CPR3 regulator
++ * @ro_sel: Array of ring oscillator values selected for each
++ * fuse corner
++ * @volt_adjust: Pointer to array which will be filled with the
++ * per-corner closed-loop adjustment voltages
++ * @volt_adjust_fuse: Pointer to array which will be filled with the
++ * per-fuse-corner closed-loop adjustment voltages
++ * @ro_scale: Pointer to array which will be filled with the
++ * per-fuse-corner RO scaling factor values with units of
++ * QUOT/V
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_parse_closed_loop_voltage_adjustments(
++ struct cpr3_regulator *vreg, u64 *ro_sel,
++ int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
++{
++ int i, rc;
++ u32 *ro_all_scale;
++
++ char volt_adj[] = "qcom,cpr-closed-loop-voltage-adjustment";
++ char volt_fuse_adj[] = "qcom,cpr-closed-loop-voltage-fuse-adjustment";
++ char ro_scaling[] = "qcom,cpr-ro-scaling-factor";
++
++ if (!of_find_property(vreg->of_node, volt_adj, NULL)
++ && !of_find_property(vreg->of_node, volt_fuse_adj, NULL)
++ && !vreg->aging_allowed) {
++ /* No adjustment required. */
++ return 0;
++ } else if (!of_find_property(vreg->of_node, ro_scaling, NULL)) {
++ cpr3_err(vreg, "Missing %s required for closed-loop voltage adjustment.\n",
++ ro_scaling);
++ return -EINVAL;
++ }
++
++ ro_all_scale = kcalloc(vreg->fuse_corner_count * CPR3_RO_COUNT,
++ sizeof(*ro_all_scale), GFP_KERNEL);
++ if (!ro_all_scale)
++ return -ENOMEM;
++
++ rc = cpr3_parse_array_property(vreg, ro_scaling,
++ vreg->fuse_corner_count * CPR3_RO_COUNT, ro_all_scale);
++ if (rc) {
++ cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ for (i = 0; i < vreg->fuse_corner_count; i++)
++ ro_scale[i] = ro_all_scale[i * CPR3_RO_COUNT + ro_sel[i]];
++
++ for (i = 0; i < vreg->corner_count; i++)
++ memcpy(vreg->corner[i].ro_scale,
++ &ro_all_scale[vreg->corner[i].cpr_fuse_corner * CPR3_RO_COUNT],
++ sizeof(*ro_all_scale) * CPR3_RO_COUNT);
++
++ if (of_find_property(vreg->of_node, volt_fuse_adj, NULL)) {
++ rc = cpr3_parse_array_property(vreg, volt_fuse_adj,
++ vreg->fuse_corner_count, volt_adjust_fuse);
++ if (rc) {
++ cpr3_err(vreg, "could not load closed-loop fused voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ if (of_find_property(vreg->of_node, volt_adj, NULL)) {
++ rc = cpr3_parse_corner_array_property(vreg, volt_adj,
++ 1, volt_adjust);
++ if (rc) {
++ cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++done:
++ kfree(ro_all_scale);
++ return rc;
++}
++
++/**
++ * cpr3_apm_init() - initialize APM data for a CPR3 controller
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * This function loads memory array power mux (APM) data from device tree
++ * if it is present and requests a handle to the appropriate APM controller
++ * device.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_apm_init(struct cpr3_controller *ctrl)
++{
++ struct device_node *node = ctrl->dev->of_node;
++ int rc;
++
++ if (!of_find_property(node, "qcom,apm-ctrl", NULL)) {
++ /* No APM used */
++ return 0;
++ }
++
++ ctrl->apm = msm_apm_ctrl_dev_get(ctrl->dev);
++ if (IS_ERR(ctrl->apm)) {
++ rc = PTR_ERR(ctrl->apm);
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "APM get failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ rc = of_property_read_u32(node, "qcom,apm-threshold-voltage",
++ &ctrl->apm_threshold_volt);
++ if (rc) {
++ cpr3_err(ctrl, "error reading qcom,apm-threshold-voltage, rc=%d\n",
++ rc);
++ return rc;
++ }
++ ctrl->apm_threshold_volt
++ = CPR3_ROUND(ctrl->apm_threshold_volt, ctrl->step_volt);
++
++ /* No error check since this is an optional property. */
++ of_property_read_u32(node, "qcom,apm-hysteresis-voltage",
++ &ctrl->apm_adj_volt);
++ ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
++
++ ctrl->apm_high_supply = MSM_APM_SUPPLY_APCC;
++ ctrl->apm_low_supply = MSM_APM_SUPPLY_MX;
++
++ return 0;
++}
++
++/**
++ * cpr3_mem_acc_init() - initialize mem-acc regulator data for
++ * a CPR3 regulator
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_mem_acc_init(struct cpr3_regulator *vreg)
++{
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ u32 *temp;
++ int i, rc;
++
++ if (!ctrl->mem_acc_regulator) {
++ cpr3_info(ctrl, "not using memory accelerator regulator\n");
++ return 0;
++ }
++
++ temp = kcalloc(vreg->corner_count, sizeof(*temp), GFP_KERNEL);
++ if (!temp)
++ return -ENOMEM;
++
++ rc = cpr3_parse_corner_array_property(vreg, "qcom,mem-acc-voltage",
++ 1, temp);
++ if (rc) {
++ cpr3_err(ctrl, "could not load mem-acc corners, rc=%d\n", rc);
++ } else {
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].mem_acc_volt = temp[i];
++ }
++
++ kfree(temp);
++ return rc;
++}
++
++/**
++ * cpr4_load_core_and_temp_adj() - parse amount of voltage adjustment for
++ * per-online-core and per-temperature voltage adjustment for a
++ * given corner or corner band from device tree.
++ * @vreg: Pointer to the CPR3 regulator
++ * @num: Corner number or corner band number
++ * @use_corner_band: Boolean indicating if the CPR3 regulator supports
++ * adjustments per corner band
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_load_core_and_temp_adj(struct cpr3_regulator *vreg,
++ int num, bool use_corner_band)
++{
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ struct cpr4_sdelta *sdelta;
++ int sdelta_size, i, j, pos, rc = 0;
++ char str[75];
++ size_t buflen;
++ char *buf;
++
++ sdelta = use_corner_band ? vreg->corner_band[num].sdelta :
++ vreg->corner[num].sdelta;
++
++ if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
++ /* corner doesn't need sdelta table */
++ sdelta->max_core_count = 0;
++ sdelta->temp_band_count = 0;
++ return rc;
++ }
++
++ sdelta_size = sdelta->max_core_count * sdelta->temp_band_count;
++ if (use_corner_band)
++ snprintf(str, sizeof(str),
++ "corner_band=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
++ num, sdelta->max_core_count,
++ sdelta->temp_band_count, sdelta_size);
++ else
++ snprintf(str, sizeof(str),
++ "corner=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n",
++ num, sdelta->max_core_count,
++ sdelta->temp_band_count, sdelta_size);
++
++ cpr3_debug(vreg, "%s", str);
++
++ sdelta->table = devm_kcalloc(ctrl->dev, sdelta_size,
++ sizeof(*sdelta->table), GFP_KERNEL);
++ if (!sdelta->table)
++ return -ENOMEM;
++
++ if (use_corner_band)
++ snprintf(str, sizeof(str),
++ "qcom,cpr-corner-band%d-temp-core-voltage-adjustment",
++ num + CPR3_CORNER_OFFSET);
++ else
++ snprintf(str, sizeof(str),
++ "qcom,cpr-corner%d-temp-core-voltage-adjustment",
++ num + CPR3_CORNER_OFFSET);
++
++ rc = cpr3_parse_array_property(vreg, str, sdelta_size,
++ sdelta->table);
++ if (rc) {
++ cpr3_err(vreg, "could not load %s, rc=%d\n", str, rc);
++ return rc;
++ }
++
++ /*
++ * Convert sdelta margins from uV to PMIC steps and apply negation to
++ * follow the SDELTA register semantics.
++ */
++ for (i = 0; i < sdelta_size; i++)
++ sdelta->table[i] = -(sdelta->table[i] / ctrl->step_volt);
++
++ buflen = sizeof(*buf) * sdelta_size * (MAX_CHARS_PER_INT + 2);
++ buf = kzalloc(buflen, GFP_KERNEL);
++ if (!buf)
++ return rc;
++
++ for (i = 0; i < sdelta->max_core_count; i++) {
++ for (j = 0, pos = 0; j < sdelta->temp_band_count; j++)
++ pos += scnprintf(buf + pos, buflen - pos, " %u",
++ sdelta->table[i * sdelta->temp_band_count + j]);
++ cpr3_debug(vreg, "sdelta[%d]:%s\n", i, buf);
++ }
++
++ kfree(buf);
++ return rc;
++}
++
++/**
++ * cpr4_parse_core_count_temp_voltage_adj() - parse configuration data for
++ * per-online-core and per-temperature voltage adjustment for
++ * a CPR3 regulator from device tree.
++ * @vreg: Pointer to the CPR3 regulator
++ * @use_corner_band: Boolean indicating if the CPR3 regulator supports
++ * adjustments per corner band
++ *
++ * This function supports parsing of per-online-core and per-temperature
++ * adjustments per corner or per corner band. CPR controllers which support
++ * corner bands apply the same adjustments to all corners within a corner band.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr4_parse_core_count_temp_voltage_adj(
++ struct cpr3_regulator *vreg, bool use_corner_band)
++{
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ struct device_node *node = vreg->of_node;
++ struct cpr3_corner *corner;
++ struct cpr4_sdelta *sdelta;
++ int i, sdelta_table_count, rc = 0;
++ int *allow_core_count_adj = NULL, *allow_temp_adj = NULL;
++ char prop_str[75];
++
++ if (of_find_property(node, use_corner_band ?
++ "qcom,corner-band-allow-temp-adjustment"
++ : "qcom,corner-allow-temp-adjustment", NULL)) {
++ if (!ctrl->allow_temp_adj) {
++ cpr3_err(ctrl, "Temperature adjustment configurations missing\n");
++ return -EINVAL;
++ }
++
++ vreg->allow_temp_adj = true;
++ }
++
++ if (of_find_property(node, use_corner_band ?
++ "qcom,corner-band-allow-core-count-adjustment"
++ : "qcom,corner-allow-core-count-adjustment",
++ NULL)) {
++ rc = of_property_read_u32(node, "qcom,max-core-count",
++ &vreg->max_core_count);
++ if (rc) {
++ cpr3_err(vreg, "error reading qcom,max-core-count, rc=%d\n",
++ rc);
++ return -EINVAL;
++ }
++
++ vreg->allow_core_count_adj = true;
++ ctrl->allow_core_count_adj = true;
++ }
++
++ if (!vreg->allow_temp_adj && !vreg->allow_core_count_adj) {
++ /*
++ * Both per-online-core and temperature based adjustments are
++ * disabled for this regulator.
++ */
++ return 0;
++ } else if (!vreg->allow_core_count_adj) {
++ /*
++ * Only per-temperature voltage adjusments are allowed.
++ * Keep max core count value as 1 to allocate SDELTA.
++ */
++ vreg->max_core_count = 1;
++ }
++
++ if (vreg->allow_core_count_adj) {
++ allow_core_count_adj = kcalloc(vreg->corner_count,
++ sizeof(*allow_core_count_adj),
++ GFP_KERNEL);
++ if (!allow_core_count_adj)
++ return -ENOMEM;
++
++ snprintf(prop_str, sizeof(prop_str), "%s", use_corner_band ?
++ "qcom,corner-band-allow-core-count-adjustment" :
++ "qcom,corner-allow-core-count-adjustment");
++
++ rc = use_corner_band ?
++ cpr3_parse_corner_band_array_property(vreg, prop_str,
++ 1, allow_core_count_adj) :
++ cpr3_parse_corner_array_property(vreg, prop_str,
++ 1, allow_core_count_adj);
++ if (rc) {
++ cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
++ rc);
++ goto done;
++ }
++ }
++
++ if (vreg->allow_temp_adj) {
++ allow_temp_adj = kcalloc(vreg->corner_count,
++ sizeof(*allow_temp_adj), GFP_KERNEL);
++ if (!allow_temp_adj) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ snprintf(prop_str, sizeof(prop_str), "%s", use_corner_band ?
++ "qcom,corner-band-allow-temp-adjustment" :
++ "qcom,corner-allow-temp-adjustment");
++
++ rc = use_corner_band ?
++ cpr3_parse_corner_band_array_property(vreg, prop_str,
++ 1, allow_temp_adj) :
++ cpr3_parse_corner_array_property(vreg, prop_str,
++ 1, allow_temp_adj);
++ if (rc) {
++ cpr3_err(vreg, "error reading %s, rc=%d\n", prop_str,
++ rc);
++ goto done;
++ }
++ }
++
++ sdelta_table_count = use_corner_band ? vreg->corner_band_count :
++ vreg->corner_count;
++
++ for (i = 0; i < sdelta_table_count; i++) {
++ sdelta = devm_kzalloc(ctrl->dev, sizeof(*corner->sdelta),
++ GFP_KERNEL);
++ if (!sdelta) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ if (allow_core_count_adj)
++ sdelta->allow_core_count_adj = allow_core_count_adj[i];
++ if (allow_temp_adj)
++ sdelta->allow_temp_adj = allow_temp_adj[i];
++ sdelta->max_core_count = vreg->max_core_count;
++ sdelta->temp_band_count = ctrl->temp_band_count;
++
++ if (use_corner_band)
++ vreg->corner_band[i].sdelta = sdelta;
++ else
++ vreg->corner[i].sdelta = sdelta;
++
++ rc = cpr4_load_core_and_temp_adj(vreg, i, use_corner_band);
++ if (rc) {
++ cpr3_err(vreg, "corner/band %d core and temp adjustment loading failed, rc=%d\n",
++ i, rc);
++ goto done;
++ }
++ }
++
++done:
++ kfree(allow_core_count_adj);
++ kfree(allow_temp_adj);
++
++ return rc;
++}
++
++/**
++ * cprh_adjust_voltages_for_apm() - adjust per-corner floor and ceiling voltages
++ * so that they do not overlap the APM threshold voltage.
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * The memory array power mux (APM) must be configured for a specific supply
++ * based upon where the VDD voltage lies with respect to the APM threshold
++ * voltage. When using CPR hardware closed-loop, the voltage may vary anywhere
++ * between the floor and ceiling voltage without software notification.
++ * Therefore, it is required that the floor to ceiling range for every corner
++ * not intersect the APM threshold voltage. This function adjusts the floor to
++ * ceiling range for each corner which violates this requirement.
++ *
++ * The following algorithm is applied:
++ * if floor < threshold <= ceiling:
++ * if open_loop >= threshold, then floor = threshold - adj
++ * else ceiling = threshold - step
++ * where:
++ * adj = APM hysteresis voltage established to minimize the number of
++ * corners with artificially increased floor voltages
++ * step = voltage in microvolts of a single step of the VDD supply
++ *
++ * The open-loop voltage is also bounded by the new floor or ceiling value as
++ * needed.
++ *
++ * Return: none
++ */
++void cprh_adjust_voltages_for_apm(struct cpr3_regulator *vreg)
++{
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ struct cpr3_corner *corner;
++ int i, adj, threshold, prev_ceiling, prev_floor, prev_open_loop;
++
++ if (!ctrl->apm_threshold_volt) {
++ /* APM not being used. */
++ return;
++ }
++
++ ctrl->apm_threshold_volt = CPR3_ROUND(ctrl->apm_threshold_volt,
++ ctrl->step_volt);
++ ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
++
++ threshold = ctrl->apm_threshold_volt;
++ adj = ctrl->apm_adj_volt;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ corner = &vreg->corner[i];
++
++ if (threshold <= corner->floor_volt
++ || threshold > corner->ceiling_volt)
++ continue;
++
++ prev_floor = corner->floor_volt;
++ prev_ceiling = corner->ceiling_volt;
++ prev_open_loop = corner->open_loop_volt;
++
++ if (corner->open_loop_volt >= threshold) {
++ corner->floor_volt = max(corner->floor_volt,
++ threshold - adj);
++ if (corner->open_loop_volt < corner->floor_volt)
++ corner->open_loop_volt = corner->floor_volt;
++ } else {
++ corner->ceiling_volt = threshold - ctrl->step_volt;
++ }
++
++ if (corner->floor_volt != prev_floor
++ || corner->ceiling_volt != prev_ceiling
++ || corner->open_loop_volt != prev_open_loop)
++ cpr3_debug(vreg, "APM threshold=%d, APM adj=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
++ threshold, adj, i, prev_floor, prev_ceiling,
++ prev_open_loop, corner->floor_volt,
++ corner->ceiling_volt, corner->open_loop_volt);
++ }
++}
++
++/**
++ * cprh_adjust_voltages_for_mem_acc() - adjust per-corner floor and ceiling
++ * voltages so that they do not intersect the MEM ACC threshold
++ * voltage
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * The following algorithm is applied:
++ * if floor < threshold <= ceiling:
++ * if open_loop >= threshold, then floor = threshold
++ * else ceiling = threshold - step
++ * where:
++ * step = voltage in microvolts of a single step of the VDD supply
++ *
++ * The open-loop voltage is also bounded by the new floor or ceiling value as
++ * needed.
++ *
++ * Return: none
++ */
++void cprh_adjust_voltages_for_mem_acc(struct cpr3_regulator *vreg)
++{
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ struct cpr3_corner *corner;
++ int i, threshold, prev_ceiling, prev_floor, prev_open_loop;
++
++ if (!ctrl->mem_acc_threshold_volt) {
++ /* MEM ACC not being used. */
++ return;
++ }
++
++ ctrl->mem_acc_threshold_volt = CPR3_ROUND(ctrl->mem_acc_threshold_volt,
++ ctrl->step_volt);
++
++ threshold = ctrl->mem_acc_threshold_volt;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ corner = &vreg->corner[i];
++
++ if (threshold <= corner->floor_volt
++ || threshold > corner->ceiling_volt)
++ continue;
++
++ prev_floor = corner->floor_volt;
++ prev_ceiling = corner->ceiling_volt;
++ prev_open_loop = corner->open_loop_volt;
++
++ if (corner->open_loop_volt >= threshold) {
++ corner->floor_volt = max(corner->floor_volt, threshold);
++ if (corner->open_loop_volt < corner->floor_volt)
++ corner->open_loop_volt = corner->floor_volt;
++ } else {
++ corner->ceiling_volt = threshold - ctrl->step_volt;
++ }
++
++ if (corner->floor_volt != prev_floor
++ || corner->ceiling_volt != prev_ceiling
++ || corner->open_loop_volt != prev_open_loop)
++ cpr3_debug(vreg, "MEM ACC threshold=%d changed corner %d voltages; prev: floor=%d, ceiling=%d, open-loop=%d; new: floor=%d, ceiling=%d, open-loop=%d\n",
++ threshold, i, prev_floor, prev_ceiling,
++ prev_open_loop, corner->floor_volt,
++ corner->ceiling_volt, corner->open_loop_volt);
++ }
++}
++
++/**
++ * cpr3_apply_closed_loop_offset_voltages() - modify the closed-loop voltage
++ * adjustments by the amounts that are needed for this
++ * fuse combo
++ * @vreg: Pointer to the CPR3 regulator
++ * @volt_adjust: Array of closed-loop voltage adjustment values of length
++ * vreg->corner_count which is further adjusted based upon
++ * offset voltage fuse values.
++ * @fuse_volt_adjust: Fused closed-loop voltage adjustment values of length
++ * vreg->fuse_corner_count.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr3_apply_closed_loop_offset_voltages(struct cpr3_regulator *vreg,
++ int *volt_adjust, int *fuse_volt_adjust)
++{
++ u32 *corner_map;
++ int rc = 0, i;
++
++ if (!of_find_property(vreg->of_node,
++ "qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL)) {
++ /* No closed-loop offset required. */
++ return 0;
++ }
++
++ corner_map = kcalloc(vreg->corner_count, sizeof(*corner_map),
++ GFP_KERNEL);
++ if (!corner_map)
++ return -ENOMEM;
++
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,cpr-fused-closed-loop-voltage-adjustment-map",
++ 1, corner_map);
++ if (rc)
++ goto done;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ if (corner_map[i] == 0) {
++ continue;
++ } else if (corner_map[i] > vreg->fuse_corner_count) {
++ cpr3_err(vreg, "corner %d mapped to invalid fuse corner: %u\n",
++ i, corner_map[i]);
++ rc = -EINVAL;
++ goto done;
++ }
++
++ volt_adjust[i] += fuse_volt_adjust[corner_map[i] - 1];
++ }
++
++done:
++ kfree(corner_map);
++ return rc;
++}
++
++/**
++ * cpr3_enforce_inc_quotient_monotonicity() - Ensure that target quotients
++ * increase monotonically from lower to higher corners
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static void cpr3_enforce_inc_quotient_monotonicity(struct cpr3_regulator *vreg)
++{
++ int i, j;
++
++ for (i = 1; i < vreg->corner_count; i++) {
++ for (j = 0; j < CPR3_RO_COUNT; j++) {
++ if (vreg->corner[i].target_quot[j]
++ && vreg->corner[i].target_quot[j]
++ < vreg->corner[i - 1].target_quot[j]) {
++ cpr3_debug(vreg, "corner %d RO%u target quot=%u < corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
++ i, j,
++ vreg->corner[i].target_quot[j],
++ i - 1, j,
++ vreg->corner[i - 1].target_quot[j],
++ i, j,
++ vreg->corner[i - 1].target_quot[j]);
++ vreg->corner[i].target_quot[j]
++ = vreg->corner[i - 1].target_quot[j];
++ }
++ }
++ }
++}
++
++/**
++ * cpr3_enforce_dec_quotient_monotonicity() - Ensure that target quotients
++ * decrease monotonically from higher to lower corners
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static void cpr3_enforce_dec_quotient_monotonicity(struct cpr3_regulator *vreg)
++{
++ int i, j;
++
++ for (i = vreg->corner_count - 2; i >= 0; i--) {
++ for (j = 0; j < CPR3_RO_COUNT; j++) {
++ if (vreg->corner[i + 1].target_quot[j]
++ && vreg->corner[i].target_quot[j]
++ > vreg->corner[i + 1].target_quot[j]) {
++ cpr3_debug(vreg, "corner %d RO%u target quot=%u > corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
++ i, j,
++ vreg->corner[i].target_quot[j],
++ i + 1, j,
++ vreg->corner[i + 1].target_quot[j],
++ i, j,
++ vreg->corner[i + 1].target_quot[j]);
++ vreg->corner[i].target_quot[j]
++ = vreg->corner[i + 1].target_quot[j];
++ }
++ }
++ }
++}
++
++/**
++ * _cpr3_adjust_target_quotients() - adjust the target quotients for each
++ * corner of the regulator according to input adjustment and
++ * scaling arrays
++ * @vreg: Pointer to the CPR3 regulator
++ * @volt_adjust: Pointer to an array of closed-loop voltage adjustments
++ * with units of microvolts. The array must have
++ * vreg->corner_count number of elements.
++ * @ro_scale: Pointer to a flattened 2D array of RO scaling factors.
++ * The array must have an inner dimension of CPR3_RO_COUNT
++ * and an outer dimension of vreg->corner_count
++ * @label: Null terminated string providing a label for the type
++ * of adjustment.
++ *
++ * Return: true if any corners received a positive voltage adjustment (> 0),
++ * else false
++ */
++static bool _cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
++ const int *volt_adjust, const int *ro_scale, const char *label)
++{
++ int i, j, quot_adjust;
++ bool is_increasing = false;
++ u32 prev_quot;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ for (j = 0; j < CPR3_RO_COUNT; j++) {
++ if (vreg->corner[i].target_quot[j]) {
++ quot_adjust = cpr3_quot_adjustment(
++ ro_scale[i * CPR3_RO_COUNT + j],
++ volt_adjust[i]);
++ if (quot_adjust) {
++ prev_quot = vreg->corner[i].
++ target_quot[j];
++ vreg->corner[i].target_quot[j]
++ += quot_adjust;
++ cpr3_debug(vreg, "adjusted corner %d RO%d target quot %s: %u --> %u (%d uV)\n",
++ i, j, label, prev_quot,
++ vreg->corner[i].target_quot[j],
++ volt_adjust[i]);
++ }
++ }
++ }
++ if (volt_adjust[i] > 0)
++ is_increasing = true;
++ }
++
++ return is_increasing;
++}
++
++/**
++ * cpr3_adjust_target_quotients() - adjust the target quotients for each
++ * corner according to device tree values and fuse values
++ * @vreg: Pointer to the CPR3 regulator
++ * @fuse_volt_adjust: Fused closed-loop voltage adjustment values of length
++ * vreg->fuse_corner_count. This parameter could be null
++ * pointer when no fused adjustments are needed.
++ *
++ * Return: 0 on success, errno on failure
++ */
++int cpr3_adjust_target_quotients(struct cpr3_regulator *vreg,
++ int *fuse_volt_adjust)
++{
++ int i, rc;
++ int *volt_adjust, *ro_scale;
++ bool explicit_adjustment, fused_adjustment, is_increasing;
++
++ explicit_adjustment = of_find_property(vreg->of_node,
++ "qcom,cpr-closed-loop-voltage-adjustment", NULL);
++ fused_adjustment = of_find_property(vreg->of_node,
++ "qcom,cpr-fused-closed-loop-voltage-adjustment-map", NULL);
++
++ if (!explicit_adjustment && !fused_adjustment && !vreg->aging_allowed) {
++ /* No adjustment required. */
++ return 0;
++ } else if (!of_find_property(vreg->of_node,
++ "qcom,cpr-ro-scaling-factor", NULL)) {
++ cpr3_err(vreg, "qcom,cpr-ro-scaling-factor is required for closed-loop voltage adjustment, but is missing\n");
++ return -EINVAL;
++ }
++
++ volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
++ GFP_KERNEL);
++ ro_scale = kcalloc(vreg->corner_count * CPR3_RO_COUNT,
++ sizeof(*ro_scale), GFP_KERNEL);
++ if (!volt_adjust || !ro_scale) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,cpr-ro-scaling-factor", CPR3_RO_COUNT, ro_scale);
++ if (rc) {
++ cpr3_err(vreg, "could not load RO scaling factors, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ for (i = 0; i < vreg->corner_count; i++)
++ memcpy(vreg->corner[i].ro_scale, &ro_scale[i * CPR3_RO_COUNT],
++ sizeof(*ro_scale) * CPR3_RO_COUNT);
++
++ if (explicit_adjustment) {
++ rc = cpr3_parse_corner_array_property(vreg,
++ "qcom,cpr-closed-loop-voltage-adjustment",
++ 1, volt_adjust);
++ if (rc) {
++ cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ _cpr3_adjust_target_quotients(vreg, volt_adjust, ro_scale,
++ "from DT");
++ cpr3_enforce_inc_quotient_monotonicity(vreg);
++ }
++
++ if (fused_adjustment && fuse_volt_adjust) {
++ memset(volt_adjust, 0,
++ sizeof(*volt_adjust) * vreg->corner_count);
++
++ rc = cpr3_apply_closed_loop_offset_voltages(vreg, volt_adjust,
++ fuse_volt_adjust);
++ if (rc) {
++ cpr3_err(vreg, "could not apply fused closed-loop voltage reductions, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ is_increasing = _cpr3_adjust_target_quotients(vreg, volt_adjust,
++ ro_scale, "from fuse");
++ if (is_increasing)
++ cpr3_enforce_inc_quotient_monotonicity(vreg);
++ else
++ cpr3_enforce_dec_quotient_monotonicity(vreg);
++ }
++
++done:
++ kfree(volt_adjust);
++ kfree(ro_scale);
++ return rc;
++}
+--- /dev/null
++++ b/drivers/regulator/cpr4-apss-regulator.c
+@@ -0,0 +1,1819 @@
++/*
++ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ */
++
++#define pr_fmt(fmt) "%s: " fmt, __func__
++
++#include <linux/bitops.h>
++#include <linux/debugfs.h>
++#include <linux/err.h>
++#include <linux/init.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/kernel.h>
++#include <linux/list.h>
++#include <linux/module.h>
++#include <linux/of.h>
++#include <linux/of_device.h>
++#include <linux/platform_device.h>
++#include <linux/pm_opp.h>
++#include <linux/slab.h>
++#include <linux/string.h>
++#include <linux/uaccess.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/machine.h>
++#include <linux/regulator/of_regulator.h>
++
++#include "cpr3-regulator.h"
++
++#define IPQ807x_APSS_FUSE_CORNERS 4
++#define IPQ817x_APPS_FUSE_CORNERS 2
++#define IPQ6018_APSS_FUSE_CORNERS 4
++#define IPQ9574_APSS_FUSE_CORNERS 4
++
++u32 g_valid_fuse_count = IPQ807x_APSS_FUSE_CORNERS;
++
++/**
++ * struct cpr4_ipq807x_apss_fuses - APSS specific fuse data for IPQ807x
++ * @ro_sel: Ring oscillator select fuse parameter value for each
++ * fuse corner
++ * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
++ * for each fuse corner (raw, not converted to a voltage)
++ * @target_quot: CPR target quotient fuse parameter value for each fuse
++ * corner
++ * @quot_offset: CPR target quotient offset fuse parameter value for each
++ * fuse corner (raw, not unpacked) used for target quotient
++ * interpolation
++ * @speed_bin: Application processor speed bin fuse parameter value for
++ * the given chip
++ * @cpr_fusing_rev: CPR fusing revision fuse parameter value
++ * @boost_cfg: CPR boost configuration fuse parameter value
++ * @boost_voltage: CPR boost voltage fuse parameter value (raw, not
++ * converted to a voltage)
++ *
++ * This struct holds the values for all of the fuses read from memory.
++ */
++struct cpr4_ipq807x_apss_fuses {
++ u64 ro_sel[IPQ807x_APSS_FUSE_CORNERS];
++ u64 init_voltage[IPQ807x_APSS_FUSE_CORNERS];
++ u64 target_quot[IPQ807x_APSS_FUSE_CORNERS];
++ u64 quot_offset[IPQ807x_APSS_FUSE_CORNERS];
++ u64 speed_bin;
++ u64 cpr_fusing_rev;
++ u64 boost_cfg;
++ u64 boost_voltage;
++ u64 misc;
++};
++
++/*
++ * fuse combo = fusing revision + 8 * (speed bin)
++ * where: fusing revision = 0 - 7 and speed bin = 0 - 7
++ */
++#define CPR4_IPQ807x_APSS_FUSE_COMBO_COUNT 64
++
++/*
++ * Constants which define the name of each fuse corner.
++ */
++enum cpr4_ipq807x_apss_fuse_corner {
++ CPR4_IPQ807x_APSS_FUSE_CORNER_SVS = 0,
++ CPR4_IPQ807x_APSS_FUSE_CORNER_NOM = 1,
++ CPR4_IPQ807x_APSS_FUSE_CORNER_TURBO = 2,
++ CPR4_IPQ807x_APSS_FUSE_CORNER_STURBO = 3,
++};
++
++static const char * const cpr4_ipq807x_apss_fuse_corner_name[] = {
++ [CPR4_IPQ807x_APSS_FUSE_CORNER_SVS] = "SVS",
++ [CPR4_IPQ807x_APSS_FUSE_CORNER_NOM] = "NOM",
++ [CPR4_IPQ807x_APSS_FUSE_CORNER_TURBO] = "TURBO",
++ [CPR4_IPQ807x_APSS_FUSE_CORNER_STURBO] = "STURBO",
++};
++
++/*
++ * IPQ807x APSS fuse parameter locations:
++ *
++ * Structs are organized with the following dimensions:
++ * Outer: 0 to 3 for fuse corners from lowest to highest corner
++ * Inner: large enough to hold the longest set of parameter segments which
++ * fully defines a fuse parameter, +1 (for NULL termination).
++ * Each segment corresponds to a contiguous group of bits from a
++ * single fuse row. These segments are concatentated together in
++ * order to form the full fuse parameter value. The segments for
++ * a given parameter may correspond to different fuse rows.
++ */
++static struct cpr3_fuse_param
++ipq807x_apss_ro_sel_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
++ {{73, 8, 11}, {} },
++ {{73, 4, 7}, {} },
++ {{73, 0, 3}, {} },
++ {{73, 12, 15}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq807x_apss_init_voltage_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
++ {{71, 18, 23}, {} },
++ {{71, 12, 17}, {} },
++ {{71, 6, 11}, {} },
++ {{71, 0, 5}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq807x_apss_target_quot_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
++ {{72, 32, 43}, {} },
++ {{72, 20, 31}, {} },
++ {{72, 8, 19}, {} },
++ {{72, 44, 55}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq807x_apss_quot_offset_param[IPQ807x_APSS_FUSE_CORNERS][2] = {
++ {{} },
++ {{71, 46, 52}, {} },
++ {{71, 39, 45}, {} },
++ {{71, 32, 38}, {} },
++};
++
++static struct cpr3_fuse_param ipq807x_cpr_fusing_rev_param[] = {
++ {71, 53, 55},
++ {},
++};
++
++static struct cpr3_fuse_param ipq807x_apss_speed_bin_param[] = {
++ {36, 40, 42},
++ {},
++};
++
++static struct cpr3_fuse_param ipq807x_cpr_boost_fuse_cfg_param[] = {
++ {36, 43, 45},
++ {},
++};
++
++static struct cpr3_fuse_param ipq807x_apss_boost_fuse_volt_param[] = {
++ {71, 0, 5},
++ {},
++};
++
++static struct cpr3_fuse_param ipq807x_misc_fuse_volt_adj_param[] = {
++ {36, 54, 54},
++ {},
++};
++
++static struct cpr3_fuse_parameters ipq807x_fuse_params = {
++ .apss_ro_sel_param = ipq807x_apss_ro_sel_param,
++ .apss_init_voltage_param = ipq807x_apss_init_voltage_param,
++ .apss_target_quot_param = ipq807x_apss_target_quot_param,
++ .apss_quot_offset_param = ipq807x_apss_quot_offset_param,
++ .cpr_fusing_rev_param = ipq807x_cpr_fusing_rev_param,
++ .apss_speed_bin_param = ipq807x_apss_speed_bin_param,
++ .cpr_boost_fuse_cfg_param = ipq807x_cpr_boost_fuse_cfg_param,
++ .apss_boost_fuse_volt_param = ipq807x_apss_boost_fuse_volt_param,
++ .misc_fuse_volt_adj_param = ipq807x_misc_fuse_volt_adj_param
++};
++
++/*
++ * The number of possible values for misc fuse is
++ * 2^(#bits defined for misc fuse)
++ */
++#define IPQ807x_MISC_FUSE_VAL_COUNT BIT(1)
++
++/*
++ * Open loop voltage fuse reference voltages in microvolts for IPQ807x
++ */
++static int ipq807x_apss_fuse_ref_volt
++ [IPQ807x_APSS_FUSE_CORNERS] = {
++ 720000,
++ 864000,
++ 992000,
++ 1064000,
++};
++
++#define IPQ807x_APSS_FUSE_STEP_VOLT 8000
++#define IPQ807x_APSS_VOLTAGE_FUSE_SIZE 6
++#define IPQ807x_APSS_QUOT_OFFSET_SCALE 5
++
++#define IPQ807x_APSS_CPR_SENSOR_COUNT 6
++
++#define IPQ807x_APSS_CPR_CLOCK_RATE 19200000
++
++#define IPQ807x_APSS_MAX_TEMP_POINTS 3
++#define IPQ807x_APSS_TEMP_SENSOR_ID_START 4
++#define IPQ807x_APSS_TEMP_SENSOR_ID_END 13
++/*
++ * Boost voltage fuse reference and ceiling voltages in microvolts for
++ * IPQ807x.
++ */
++#define IPQ807x_APSS_BOOST_FUSE_REF_VOLT 1140000
++#define IPQ807x_APSS_BOOST_CEILING_VOLT 1140000
++#define IPQ807x_APSS_BOOST_FLOOR_VOLT 900000
++#define MAX_BOOST_CONFIG_FUSE_VALUE 8
++
++#define IPQ807x_APSS_CPR_SDELTA_CORE_COUNT 15
++
++#define IPQ807x_APSS_CPR_TCSR_START 8
++#define IPQ807x_APSS_CPR_TCSR_END 9
++
++/*
++ * Array of integer values mapped to each of the boost config fuse values to
++ * indicate boost enable/disable status.
++ */
++static bool boost_fuse[MAX_BOOST_CONFIG_FUSE_VALUE] = {0, 1, 1, 1, 1, 1, 1, 1};
++
++/*
++ * IPQ6018 (Few parameters are changed, remaining are same as IPQ807x)
++ */
++#define IPQ6018_APSS_FUSE_STEP_VOLT 12500
++#define IPQ6018_APSS_CPR_CLOCK_RATE 24000000
++
++static struct cpr3_fuse_param
++ipq6018_apss_ro_sel_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
++ {{75, 8, 11}, {} },
++ {{75, 4, 7}, {} },
++ {{75, 0, 3}, {} },
++ {{75, 12, 15}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq6018_apss_init_voltage_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
++ {{73, 18, 23}, {} },
++ {{73, 12, 17}, {} },
++ {{73, 6, 11}, {} },
++ {{73, 0, 5}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq6018_apss_target_quot_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
++ {{74, 32, 43}, {} },
++ {{74, 20, 31}, {} },
++ {{74, 8, 19}, {} },
++ {{74, 44, 55}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq6018_apss_quot_offset_param[IPQ6018_APSS_FUSE_CORNERS][2] = {
++ {{} },
++ {{73, 48, 55}, {} },
++ {{73, 40, 47}, {} },
++ {{73, 32, 39}, {} },
++};
++
++static struct cpr3_fuse_param ipq6018_cpr_fusing_rev_param[] = {
++ {75, 16, 18},
++ {},
++};
++
++static struct cpr3_fuse_param ipq6018_apss_speed_bin_param[] = {
++ {36, 40, 42},
++ {},
++};
++
++static struct cpr3_fuse_param ipq6018_cpr_boost_fuse_cfg_param[] = {
++ {36, 43, 45},
++ {},
++};
++
++static struct cpr3_fuse_param ipq6018_apss_boost_fuse_volt_param[] = {
++ {73, 0, 5},
++ {},
++};
++
++static struct cpr3_fuse_param ipq6018_misc_fuse_volt_adj_param[] = {
++ {36, 54, 54},
++ {},
++};
++
++static struct cpr3_fuse_parameters ipq6018_fuse_params = {
++ .apss_ro_sel_param = ipq6018_apss_ro_sel_param,
++ .apss_init_voltage_param = ipq6018_apss_init_voltage_param,
++ .apss_target_quot_param = ipq6018_apss_target_quot_param,
++ .apss_quot_offset_param = ipq6018_apss_quot_offset_param,
++ .cpr_fusing_rev_param = ipq6018_cpr_fusing_rev_param,
++ .apss_speed_bin_param = ipq6018_apss_speed_bin_param,
++ .cpr_boost_fuse_cfg_param = ipq6018_cpr_boost_fuse_cfg_param,
++ .apss_boost_fuse_volt_param = ipq6018_apss_boost_fuse_volt_param,
++ .misc_fuse_volt_adj_param = ipq6018_misc_fuse_volt_adj_param
++};
++
++
++/*
++ * Boost voltage fuse reference and ceiling voltages in microvolts for
++ * IPQ6018.
++ */
++#define IPQ6018_APSS_BOOST_FUSE_REF_VOLT 1140000
++#define IPQ6018_APSS_BOOST_CEILING_VOLT 1140000
++#define IPQ6018_APSS_BOOST_FLOOR_VOLT 900000
++
++/*
++ * Open loop voltage fuse reference voltages in microvolts for IPQ807x
++ */
++static int ipq6018_apss_fuse_ref_volt
++ [IPQ6018_APSS_FUSE_CORNERS] = {
++ 725000,
++ 862500,
++ 987500,
++ 1062500,
++};
++
++/*
++ * IPQ6018 Memory ACC settings on TCSR
++ *
++ * Turbo_L1: write TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x10
++ * write TCSR_CUSTOM_VDDAPC0_ACC_1 0x1
++ * Other modes: write TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x0
++ * write TCSR_CUSTOM_VDDAPC0_ACC_1 0x0
++ *
++ */
++#define IPQ6018_APSS_MEM_ACC_TCSR_COUNT 2
++#define TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0 0x1946178
++#define TCSR_CUSTOM_VDDAPC0_ACC_1 0x1946124
++
++struct mem_acc_tcsr {
++ u32 phy_addr;
++ void __iomem *ioremap_addr;
++ u32 value;
++};
++
++static struct mem_acc_tcsr ipq6018_mem_acc_tcsr[IPQ6018_APSS_MEM_ACC_TCSR_COUNT] = {
++ {TCSR_MEM_ACC_SW_OVERRIDE_LEGACY_APC0, NULL, 0x10},
++ {TCSR_CUSTOM_VDDAPC0_ACC_1, NULL, 0x1},
++};
++
++/*
++ * IPQ9574 (Few parameters are changed, remaining are same as IPQ6018)
++ */
++#define IPQ9574_APSS_FUSE_STEP_VOLT 10000
++
++static struct cpr3_fuse_param
++ipq9574_apss_ro_sel_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
++ {{107, 4, 7}, {} },
++ {{107, 0, 3}, {} },
++ {{106, 4, 7}, {} },
++ {{106, 0, 3}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq9574_apss_init_voltage_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
++ {{104, 24, 29}, {} },
++ {{104, 18, 23}, {} },
++ {{104, 12, 17}, {} },
++ {{104, 6, 11}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq9574_apss_target_quot_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
++ {{106, 32, 43}, {} },
++ {{106, 20, 31}, {} },
++ {{106, 8, 19}, {} },
++ {{106, 44, 55}, {} },
++};
++
++static struct cpr3_fuse_param
++ipq9574_apss_quot_offset_param[IPQ9574_APSS_FUSE_CORNERS][2] = {
++ {{} },
++ {{105, 48, 55}, {} },
++ {{105, 40, 47}, {} },
++ {{105, 32, 39}, {} },
++};
++
++static struct cpr3_fuse_param ipq9574_cpr_fusing_rev_param[] = {
++ {107, 8, 10},
++ {},
++};
++
++static struct cpr3_fuse_param ipq9574_apss_speed_bin_param[] = {
++ {0, 40, 42},
++ {},
++};
++
++static struct cpr3_fuse_param ipq9574_cpr_boost_fuse_cfg_param[] = {
++ {0, 43, 45},
++ {},
++};
++
++static struct cpr3_fuse_param ipq9574_apss_boost_fuse_volt_param[] = {
++ {104, 0, 5},
++ {},
++};
++
++static struct cpr3_fuse_param ipq9574_misc_fuse_volt_adj_param[] = {
++ {0, 54, 54},
++ {},
++};
++
++static struct cpr3_fuse_parameters ipq9574_fuse_params = {
++ .apss_ro_sel_param = ipq9574_apss_ro_sel_param,
++ .apss_init_voltage_param = ipq9574_apss_init_voltage_param,
++ .apss_target_quot_param = ipq9574_apss_target_quot_param,
++ .apss_quot_offset_param = ipq9574_apss_quot_offset_param,
++ .cpr_fusing_rev_param = ipq9574_cpr_fusing_rev_param,
++ .apss_speed_bin_param = ipq9574_apss_speed_bin_param,
++ .cpr_boost_fuse_cfg_param = ipq9574_cpr_boost_fuse_cfg_param,
++ .apss_boost_fuse_volt_param = ipq9574_apss_boost_fuse_volt_param,
++ .misc_fuse_volt_adj_param = ipq9574_misc_fuse_volt_adj_param
++};
++
++/*
++ * Open loop voltage fuse reference voltages in microvolts for IPQ9574
++ */
++static int ipq9574_apss_fuse_ref_volt
++ [IPQ9574_APSS_FUSE_CORNERS] = {
++ 725000,
++ 862500,
++ 987500,
++ 1062500,
++};
++
++/**
++ * cpr4_ipq807x_apss_read_fuse_data() - load APSS specific fuse parameter values
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * This function allocates a cpr4_ipq807x_apss_fuses struct, fills it with
++ * values read out of hardware fuses, and finally copies common fuse values
++ * into the CPR3 regulator struct.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_ipq807x_apss_read_fuse_data(struct cpr3_regulator *vreg)
++{
++ void __iomem *base = vreg->thread->ctrl->fuse_base;
++ struct cpr4_ipq807x_apss_fuses *fuse;
++ int i, rc;
++
++ fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
++ if (!fuse)
++ return -ENOMEM;
++
++ rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->apss_speed_bin_param,
++ &fuse->speed_bin);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read speed bin fuse, rc=%d\n", rc);
++ return rc;
++ }
++ cpr3_info(vreg, "speed bin = %llu\n", fuse->speed_bin);
++
++ rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->cpr_fusing_rev_param,
++ &fuse->cpr_fusing_rev);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read CPR fusing revision fuse, rc=%d\n",
++ rc);
++ return rc;
++ }
++ cpr3_info(vreg, "CPR fusing revision = %llu\n", fuse->cpr_fusing_rev);
++
++ rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->misc_fuse_volt_adj_param,
++ &fuse->misc);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read misc voltage adjustment fuse, rc=%d\n",
++ rc);
++ return rc;
++ }
++ cpr3_info(vreg, "CPR misc fuse value = %llu\n", fuse->misc);
++ if (fuse->misc >= IPQ807x_MISC_FUSE_VAL_COUNT) {
++ cpr3_err(vreg, "CPR misc fuse value = %llu, should be < %lu\n",
++ fuse->misc, IPQ807x_MISC_FUSE_VAL_COUNT);
++ return -EINVAL;
++ }
++
++ for (i = 0; i < g_valid_fuse_count; i++) {
++ rc = cpr3_read_fuse_param(base,
++ vreg->cpr4_regulator_data->cpr3_fuse_params->apss_init_voltage_param[i],
++ &fuse->init_voltage[i]);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
++ i, rc);
++ return rc;
++ }
++
++ rc = cpr3_read_fuse_param(base,
++ vreg->cpr4_regulator_data->cpr3_fuse_params->apss_target_quot_param[i],
++ &fuse->target_quot[i]);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read fuse-corner %d target quotient fuse, rc=%d\n",
++ i, rc);
++ return rc;
++ }
++
++ rc = cpr3_read_fuse_param(base,
++ vreg->cpr4_regulator_data->cpr3_fuse_params->apss_ro_sel_param[i],
++ &fuse->ro_sel[i]);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read fuse-corner %d RO select fuse, rc=%d\n",
++ i, rc);
++ return rc;
++ }
++
++ rc = cpr3_read_fuse_param(base,
++ vreg->cpr4_regulator_data->cpr3_fuse_params->apss_quot_offset_param[i],
++ &fuse->quot_offset[i]);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read fuse-corner %d quotient offset fuse, rc=%d\n",
++ i, rc);
++ return rc;
++ }
++ }
++
++ rc = cpr3_read_fuse_param(base, vreg->cpr4_regulator_data->cpr3_fuse_params->cpr_boost_fuse_cfg_param,
++ &fuse->boost_cfg);
++ if (rc) {
++ cpr3_err(vreg, "Unable to read CPR boost config fuse, rc=%d\n",
++ rc);
++ return rc;
++ }
++ cpr3_info(vreg, "Voltage boost fuse config = %llu boost = %s\n",
++ fuse->boost_cfg, boost_fuse[fuse->boost_cfg]
++ ? "enable" : "disable");
++
++ rc = cpr3_read_fuse_param(base,
++ vreg->cpr4_regulator_data->cpr3_fuse_params->apss_boost_fuse_volt_param,
++ &fuse->boost_voltage);
++ if (rc) {
++ cpr3_err(vreg, "failed to read boost fuse voltage, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
++ if (vreg->fuse_combo >= CPR4_IPQ807x_APSS_FUSE_COMBO_COUNT) {
++ cpr3_err(vreg, "invalid CPR fuse combo = %d found\n",
++ vreg->fuse_combo);
++ return -EINVAL;
++ }
++
++ vreg->speed_bin_fuse = fuse->speed_bin;
++ vreg->cpr_rev_fuse = fuse->cpr_fusing_rev;
++ vreg->fuse_corner_count = g_valid_fuse_count;
++ vreg->platform_fuses = fuse;
++
++ return 0;
++}
++
++/**
++ * cpr4_apss_parse_corner_data() - parse APSS corner data from device tree
++ * properties of the CPR3 regulator's device node
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_parse_corner_data(struct cpr3_regulator *vreg)
++{
++ struct device_node *node = vreg->of_node;
++ struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
++ u32 *temp = NULL;
++ int i, rc;
++
++ rc = cpr3_parse_common_corner_data(vreg);
++ if (rc) {
++ cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
++ return rc;
++ }
++
++ /* If fuse has incorrect RO Select values and dtsi has "qcom,cpr-ro-sel"
++ * entry with RO select values other than zero, then dtsi values will
++ * be used.
++ */
++ if (of_find_property(node, "qcom,cpr-ro-sel", NULL)) {
++ temp = kcalloc(vreg->fuse_corner_count, sizeof(*temp),
++ GFP_KERNEL);
++ if (!temp)
++ return -ENOMEM;
++
++ rc = cpr3_parse_array_property(vreg, "qcom,cpr-ro-sel",
++ vreg->fuse_corner_count, temp);
++ if (rc)
++ goto done;
++
++ for (i = 0; i < vreg->fuse_corner_count; i++) {
++ if (temp[i] != 0)
++ fuse->ro_sel[i] = temp[i];
++ }
++ }
++done:
++ kfree(temp);
++ return rc;
++}
++
++/**
++ * cpr4_apss_parse_misc_fuse_voltage_adjustments() - fill an array from a
++ * portion of the voltage adjustments specified based on
++ * miscellaneous fuse bits.
++ * @vreg: Pointer to the CPR3 regulator
++ * @volt_adjust: Voltage adjustment output data array which must be
++ * of size vreg->corner_count
++ *
++ * cpr3_parse_common_corner_data() must be called for vreg before this function
++ * is called so that speed bin size elements are initialized.
++ *
++ * Two formats are supported for the device tree property:
++ * 1. Length == tuple_list_size * vreg->corner_count
++ * (reading begins at index 0)
++ * 2. Length == tuple_list_size * vreg->speed_bin_corner_sum
++ * (reading begins at index tuple_list_size * vreg->speed_bin_offset)
++ *
++ * Here, tuple_list_size is the number of possible values for misc fuse.
++ * All other property lengths are treated as errors.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_parse_misc_fuse_voltage_adjustments(
++ struct cpr3_regulator *vreg, u32 *volt_adjust)
++{
++ struct device_node *node = vreg->of_node;
++ struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
++ int tuple_list_size = IPQ807x_MISC_FUSE_VAL_COUNT;
++ int i, offset, rc, len = 0;
++ const char *prop_name = "qcom,cpr-misc-fuse-voltage-adjustment";
++
++ if (!of_find_property(node, prop_name, &len)) {
++ cpr3_err(vreg, "property %s is missing\n", prop_name);
++ return -EINVAL;
++ }
++
++ if (len == tuple_list_size * vreg->corner_count * sizeof(u32)) {
++ offset = 0;
++ } else if (vreg->speed_bin_corner_sum > 0 &&
++ len == tuple_list_size * vreg->speed_bin_corner_sum
++ * sizeof(u32)) {
++ offset = tuple_list_size * vreg->speed_bin_offset
++ + fuse->misc * vreg->corner_count;
++ } else {
++ if (vreg->speed_bin_corner_sum > 0)
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu or %zu\n",
++ prop_name, len,
++ tuple_list_size * vreg->corner_count
++ * sizeof(u32),
++ tuple_list_size * vreg->speed_bin_corner_sum
++ * sizeof(u32));
++ else
++ cpr3_err(vreg, "property %s has invalid length=%d, should be %zu\n",
++ prop_name, len,
++ tuple_list_size * vreg->corner_count
++ * sizeof(u32));
++ return -EINVAL;
++ }
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ rc = of_property_read_u32_index(node, prop_name, offset + i,
++ &volt_adjust[i]);
++ if (rc) {
++ cpr3_err(vreg, "error reading property %s, rc=%d\n",
++ prop_name, rc);
++ return rc;
++ }
++ }
++
++ return 0;
++}
++
++/**
++ * cpr4_ipq807x_apss_calculate_open_loop_voltages() - calculate the open-loop
++ * voltage for each corner of a CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * If open-loop voltage interpolation is allowed in device tree, then
++ * this function calculates the open-loop voltage for a given corner using
++ * linear interpolation. This interpolation is performed using the processor
++ * frequencies of the lower and higher Fmax corners along with their fused
++ * open-loop voltages.
++ *
++ * If open-loop voltage interpolation is not allowed, then this function uses
++ * the Fmax fused open-loop voltage for all of the corners associated with a
++ * given fuse corner.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_ipq807x_apss_calculate_open_loop_voltages(
++ struct cpr3_regulator *vreg)
++{
++ struct device_node *node = vreg->of_node;
++ struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ int i, j, rc = 0;
++ bool allow_interpolation;
++ u64 freq_low, volt_low, freq_high, volt_high;
++ int *fuse_volt, *misc_adj_volt;
++ int *fmax_corner;
++
++ fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
++ GFP_KERNEL);
++ fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
++ GFP_KERNEL);
++ if (!fuse_volt || !fmax_corner) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ for (i = 0; i < vreg->fuse_corner_count; i++) {
++ if (ctrl->cpr_global_setting == CPR_DISABLED)
++ fuse_volt[i] = vreg->cpr4_regulator_data->fuse_ref_volt[i];
++ else
++ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
++ vreg->cpr4_regulator_data->fuse_ref_volt[i],
++ vreg->cpr4_regulator_data->fuse_step_volt,
++ fuse->init_voltage[i],
++ IPQ807x_APSS_VOLTAGE_FUSE_SIZE);
++
++ /* Log fused open-loop voltage values for debugging purposes. */
++ cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
++ cpr4_ipq807x_apss_fuse_corner_name[i],
++ fuse_volt[i]);
++ }
++
++ rc = cpr3_determine_part_type(vreg,
++ fuse_volt[vreg->fuse_corner_count - 1]);
++ if (rc) {
++ cpr3_err(vreg, "fused part type detection failed failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ rc = cpr3_adjust_fused_open_loop_voltages(vreg, fuse_volt);
++ if (rc) {
++ cpr3_err(vreg, "fused open-loop voltage adjustment failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ allow_interpolation = of_property_read_bool(node,
++ "qcom,allow-voltage-interpolation");
++
++ for (i = 1; i < vreg->fuse_corner_count; i++) {
++ if (fuse_volt[i] < fuse_volt[i - 1]) {
++ cpr3_info(vreg, "fuse corner %d voltage=%d uV < fuse corner %d voltage=%d uV; overriding: fuse corner %d voltage=%d\n",
++ i, fuse_volt[i], i - 1, fuse_volt[i - 1],
++ i, fuse_volt[i - 1]);
++ fuse_volt[i] = fuse_volt[i - 1];
++ }
++ }
++
++ if (!allow_interpolation) {
++ /* Use fused open-loop voltage for lower frequencies. */
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].open_loop_volt
++ = fuse_volt[vreg->corner[i].cpr_fuse_corner];
++ goto done;
++ }
++
++ /* Determine highest corner mapped to each fuse corner */
++ j = vreg->fuse_corner_count - 1;
++ for (i = vreg->corner_count - 1; i >= 0; i--) {
++ if (vreg->corner[i].cpr_fuse_corner == j) {
++ fmax_corner[j] = i;
++ j--;
++ }
++ }
++ if (j >= 0) {
++ cpr3_err(vreg, "invalid fuse corner mapping\n");
++ rc = -EINVAL;
++ goto done;
++ }
++
++ /*
++ * Interpolation is not possible for corners mapped to the lowest fuse
++ * corner so use the fuse corner value directly.
++ */
++ for (i = 0; i <= fmax_corner[0]; i++)
++ vreg->corner[i].open_loop_volt = fuse_volt[0];
++
++ /* Interpolate voltages for the higher fuse corners. */
++ for (i = 1; i < vreg->fuse_corner_count; i++) {
++ freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
++ volt_low = fuse_volt[i - 1];
++ freq_high = vreg->corner[fmax_corner[i]].proc_freq;
++ volt_high = fuse_volt[i];
++
++ for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
++ vreg->corner[j].open_loop_volt = cpr3_interpolate(
++ freq_low, volt_low, freq_high, volt_high,
++ vreg->corner[j].proc_freq);
++ }
++
++done:
++ if (rc == 0) {
++ cpr3_debug(vreg, "unadjusted per-corner open-loop voltages:\n");
++ for (i = 0; i < vreg->corner_count; i++)
++ cpr3_debug(vreg, "open-loop[%2d] = %d uV\n", i,
++ vreg->corner[i].open_loop_volt);
++
++ rc = cpr3_adjust_open_loop_voltages(vreg);
++ if (rc)
++ cpr3_err(vreg, "open-loop voltage adjustment failed, rc=%d\n",
++ rc);
++
++ if (of_find_property(node,
++ "qcom,cpr-misc-fuse-voltage-adjustment",
++ NULL)) {
++ misc_adj_volt = kcalloc(vreg->corner_count,
++ sizeof(*misc_adj_volt), GFP_KERNEL);
++ if (!misc_adj_volt) {
++ rc = -ENOMEM;
++ goto _exit;
++ }
++
++ rc = cpr4_apss_parse_misc_fuse_voltage_adjustments(vreg,
++ misc_adj_volt);
++ if (rc) {
++ cpr3_err(vreg, "qcom,cpr-misc-fuse-voltage-adjustment reading failed, rc=%d\n",
++ rc);
++ kfree(misc_adj_volt);
++ goto _exit;
++ }
++
++ for (i = 0; i < vreg->corner_count; i++)
++ vreg->corner[i].open_loop_volt
++ += misc_adj_volt[i];
++ kfree(misc_adj_volt);
++ }
++ }
++
++_exit:
++ kfree(fuse_volt);
++ kfree(fmax_corner);
++ return rc;
++}
++
++/**
++ * cpr4_ipq807x_apss_set_no_interpolation_quotients() - use the fused target
++ * quotient values for lower frequencies.
++ * @vreg: Pointer to the CPR3 regulator
++ * @volt_adjust: Pointer to array of per-corner closed-loop adjustment
++ * voltages
++ * @volt_adjust_fuse: Pointer to array of per-fuse-corner closed-loop
++ * adjustment voltages
++ * @ro_scale: Pointer to array of per-fuse-corner RO scaling factor
++ * values with units of QUOT/V
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_ipq807x_apss_set_no_interpolation_quotients(
++ struct cpr3_regulator *vreg, int *volt_adjust,
++ int *volt_adjust_fuse, int *ro_scale)
++{
++ struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
++ u32 quot, ro;
++ int quot_adjust;
++ int i, fuse_corner;
++
++ for (i = 0; i < vreg->corner_count; i++) {
++ fuse_corner = vreg->corner[i].cpr_fuse_corner;
++ quot = fuse->target_quot[fuse_corner];
++ quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
++ volt_adjust_fuse[fuse_corner] +
++ volt_adjust[i]);
++ ro = fuse->ro_sel[fuse_corner];
++ vreg->corner[i].target_quot[ro] = quot + quot_adjust;
++ cpr3_debug(vreg, "corner=%d RO=%u target quot=%u\n",
++ i, ro, quot);
++
++ if (quot_adjust)
++ cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %u --> %u (%d uV)\n",
++ i, ro, quot, vreg->corner[i].target_quot[ro],
++ volt_adjust_fuse[fuse_corner] +
++ volt_adjust[i]);
++ }
++
++ return 0;
++}
++
++/**
++ * cpr4_ipq807x_apss_calculate_target_quotients() - calculate the CPR target
++ * quotient for each corner of a CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * If target quotient interpolation is allowed in device tree, then this
++ * function calculates the target quotient for a given corner using linear
++ * interpolation. This interpolation is performed using the processor
++ * frequencies of the lower and higher Fmax corners along with the fused
++ * target quotient and quotient offset of the higher Fmax corner.
++ *
++ * If target quotient interpolation is not allowed, then this function uses
++ * the Fmax fused target quotient for all of the corners associated with a
++ * given fuse corner.
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_ipq807x_apss_calculate_target_quotients(
++ struct cpr3_regulator *vreg)
++{
++ struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
++ int rc;
++ bool allow_interpolation;
++ u64 freq_low, freq_high, prev_quot;
++ u64 *quot_low;
++ u64 *quot_high;
++ u32 quot, ro;
++ int i, j, fuse_corner, quot_adjust;
++ int *fmax_corner;
++ int *volt_adjust, *volt_adjust_fuse, *ro_scale;
++ int *voltage_adj_misc;
++
++ /* Log fused quotient values for debugging purposes. */
++ for (i = CPR4_IPQ807x_APSS_FUSE_CORNER_SVS;
++ i < vreg->fuse_corner_count; i++)
++ cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
++ cpr4_ipq807x_apss_fuse_corner_name[i],
++ fuse->ro_sel[i], fuse->target_quot[i],
++ fuse->ro_sel[i], fuse->quot_offset[i] *
++ IPQ807x_APSS_QUOT_OFFSET_SCALE);
++
++ allow_interpolation = of_property_read_bool(vreg->of_node,
++ "qcom,allow-quotient-interpolation");
++
++ volt_adjust = kcalloc(vreg->corner_count, sizeof(*volt_adjust),
++ GFP_KERNEL);
++ volt_adjust_fuse = kcalloc(vreg->fuse_corner_count,
++ sizeof(*volt_adjust_fuse), GFP_KERNEL);
++ ro_scale = kcalloc(vreg->fuse_corner_count, sizeof(*ro_scale),
++ GFP_KERNEL);
++ fmax_corner = kcalloc(vreg->fuse_corner_count, sizeof(*fmax_corner),
++ GFP_KERNEL);
++ quot_low = kcalloc(vreg->fuse_corner_count, sizeof(*quot_low),
++ GFP_KERNEL);
++ quot_high = kcalloc(vreg->fuse_corner_count, sizeof(*quot_high),
++ GFP_KERNEL);
++ if (!volt_adjust || !volt_adjust_fuse || !ro_scale ||
++ !fmax_corner || !quot_low || !quot_high) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ rc = cpr3_parse_closed_loop_voltage_adjustments(vreg, &fuse->ro_sel[0],
++ volt_adjust, volt_adjust_fuse, ro_scale);
++ if (rc) {
++ cpr3_err(vreg, "could not load closed-loop voltage adjustments, rc=%d\n",
++ rc);
++ goto done;
++ }
++
++ if (of_find_property(vreg->of_node,
++ "qcom,cpr-misc-fuse-voltage-adjustment", NULL)) {
++ voltage_adj_misc = kcalloc(vreg->corner_count,
++ sizeof(*voltage_adj_misc), GFP_KERNEL);
++ if (!voltage_adj_misc) {
++ rc = -ENOMEM;
++ goto done;
++ }
++
++ rc = cpr4_apss_parse_misc_fuse_voltage_adjustments(vreg,
++ voltage_adj_misc);
++ if (rc) {
++ cpr3_err(vreg, "qcom,cpr-misc-fuse-voltage-adjustment reading failed, rc=%d\n",
++ rc);
++ kfree(voltage_adj_misc);
++ goto done;
++ }
++
++ for (i = 0; i < vreg->corner_count; i++)
++ volt_adjust[i] += voltage_adj_misc[i];
++
++ kfree(voltage_adj_misc);
++ }
++
++ if (!allow_interpolation) {
++ /* Use fused target quotients for lower frequencies. */
++ return cpr4_ipq807x_apss_set_no_interpolation_quotients(
++ vreg, volt_adjust, volt_adjust_fuse, ro_scale);
++ }
++
++ /* Determine highest corner mapped to each fuse corner */
++ j = vreg->fuse_corner_count - 1;
++ for (i = vreg->corner_count - 1; i >= 0; i--) {
++ if (vreg->corner[i].cpr_fuse_corner == j) {
++ fmax_corner[j] = i;
++ j--;
++ }
++ }
++ if (j >= 0) {
++ cpr3_err(vreg, "invalid fuse corner mapping\n");
++ rc = -EINVAL;
++ goto done;
++ }
++
++ /*
++ * Interpolation is not possible for corners mapped to the lowest fuse
++ * corner so use the fuse corner value directly.
++ */
++ i = CPR4_IPQ807x_APSS_FUSE_CORNER_SVS;
++ quot_adjust = cpr3_quot_adjustment(ro_scale[i], volt_adjust_fuse[i]);
++ quot = fuse->target_quot[i] + quot_adjust;
++ quot_high[i] = quot_low[i] = quot;
++ ro = fuse->ro_sel[i];
++ if (quot_adjust)
++ cpr3_debug(vreg, "adjusted fuse corner %d RO%u target quot: %llu --> %u (%d uV)\n",
++ i, ro, fuse->target_quot[i], quot, volt_adjust_fuse[i]);
++
++ for (i = 0; i <= fmax_corner[CPR4_IPQ807x_APSS_FUSE_CORNER_SVS];
++ i++)
++ vreg->corner[i].target_quot[ro] = quot;
++
++ for (i = CPR4_IPQ807x_APSS_FUSE_CORNER_NOM;
++ i < vreg->fuse_corner_count; i++) {
++ quot_high[i] = fuse->target_quot[i];
++ if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
++ quot_low[i] = quot_high[i - 1];
++ else
++ quot_low[i] = quot_high[i]
++ - fuse->quot_offset[i]
++ * IPQ807x_APSS_QUOT_OFFSET_SCALE;
++ if (quot_high[i] < quot_low[i]) {
++ cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu; overriding: quot_high[%d]=%llu\n",
++ i, quot_high[i], i, quot_low[i],
++ i, quot_low[i]);
++ quot_high[i] = quot_low[i];
++ }
++ }
++
++ /* Perform per-fuse-corner target quotient adjustment */
++ for (i = 1; i < vreg->fuse_corner_count; i++) {
++ quot_adjust = cpr3_quot_adjustment(ro_scale[i],
++ volt_adjust_fuse[i]);
++ if (quot_adjust) {
++ prev_quot = quot_high[i];
++ quot_high[i] += quot_adjust;
++ cpr3_debug(vreg, "adjusted fuse corner %d RO%llu target quot: %llu --> %llu (%d uV)\n",
++ i, fuse->ro_sel[i], prev_quot, quot_high[i],
++ volt_adjust_fuse[i]);
++ }
++
++ if (fuse->ro_sel[i] == fuse->ro_sel[i - 1])
++ quot_low[i] = quot_high[i - 1];
++ else
++ quot_low[i] += cpr3_quot_adjustment(ro_scale[i],
++ volt_adjust_fuse[i - 1]);
++
++ if (quot_high[i] < quot_low[i]) {
++ cpr3_debug(vreg, "quot_high[%d]=%llu < quot_low[%d]=%llu after adjustment; overriding: quot_high[%d]=%llu\n",
++ i, quot_high[i], i, quot_low[i],
++ i, quot_low[i]);
++ quot_high[i] = quot_low[i];
++ }
++ }
++
++ /* Interpolate voltages for the higher fuse corners. */
++ for (i = 1; i < vreg->fuse_corner_count; i++) {
++ freq_low = vreg->corner[fmax_corner[i - 1]].proc_freq;
++ freq_high = vreg->corner[fmax_corner[i]].proc_freq;
++
++ ro = fuse->ro_sel[i];
++ for (j = fmax_corner[i - 1] + 1; j <= fmax_corner[i]; j++)
++ vreg->corner[j].target_quot[ro] = cpr3_interpolate(
++ freq_low, quot_low[i], freq_high, quot_high[i],
++ vreg->corner[j].proc_freq);
++ }
++
++ /* Perform per-corner target quotient adjustment */
++ for (i = 0; i < vreg->corner_count; i++) {
++ fuse_corner = vreg->corner[i].cpr_fuse_corner;
++ ro = fuse->ro_sel[fuse_corner];
++ quot_adjust = cpr3_quot_adjustment(ro_scale[fuse_corner],
++ volt_adjust[i]);
++ if (quot_adjust) {
++ prev_quot = vreg->corner[i].target_quot[ro];
++ vreg->corner[i].target_quot[ro] += quot_adjust;
++ cpr3_debug(vreg, "adjusted corner %d RO%u target quot: %llu --> %u (%d uV)\n",
++ i, ro, prev_quot,
++ vreg->corner[i].target_quot[ro],
++ volt_adjust[i]);
++ }
++ }
++
++ /* Ensure that target quotients increase monotonically */
++ for (i = 1; i < vreg->corner_count; i++) {
++ ro = fuse->ro_sel[vreg->corner[i].cpr_fuse_corner];
++ if (fuse->ro_sel[vreg->corner[i - 1].cpr_fuse_corner] == ro
++ && vreg->corner[i].target_quot[ro]
++ < vreg->corner[i - 1].target_quot[ro]) {
++ cpr3_debug(vreg, "adjusted corner %d RO%u target quot=%u < adjusted corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
++ i, ro, vreg->corner[i].target_quot[ro],
++ i - 1, ro, vreg->corner[i - 1].target_quot[ro],
++ i, ro, vreg->corner[i - 1].target_quot[ro]);
++ vreg->corner[i].target_quot[ro]
++ = vreg->corner[i - 1].target_quot[ro];
++ }
++ }
++
++done:
++ kfree(volt_adjust);
++ kfree(volt_adjust_fuse);
++ kfree(ro_scale);
++ kfree(fmax_corner);
++ kfree(quot_low);
++ kfree(quot_high);
++ return rc;
++}
++
++/**
++ * cpr4_apss_print_settings() - print out APSS CPR configuration settings into
++ * the kernel log for debugging purposes
++ * @vreg: Pointer to the CPR3 regulator
++ */
++static void cpr4_apss_print_settings(struct cpr3_regulator *vreg)
++{
++ struct cpr3_corner *corner;
++ int i;
++
++ cpr3_debug(vreg, "Corner: Frequency (Hz), Fuse Corner, Floor (uV), Open-Loop (uV), Ceiling (uV)\n");
++ for (i = 0; i < vreg->corner_count; i++) {
++ corner = &vreg->corner[i];
++ cpr3_debug(vreg, "%3d: %10u, %2d, %7d, %7d, %7d\n",
++ i, corner->proc_freq, corner->cpr_fuse_corner,
++ corner->floor_volt, corner->open_loop_volt,
++ corner->ceiling_volt);
++ }
++
++ if (vreg->thread->ctrl->apm)
++ cpr3_debug(vreg, "APM threshold = %d uV, APM adjust = %d uV\n",
++ vreg->thread->ctrl->apm_threshold_volt,
++ vreg->thread->ctrl->apm_adj_volt);
++}
++
++/**
++ * cpr4_apss_init_thread() - perform steps necessary to initialize the
++ * configuration data for a CPR3 thread
++ * @thread: Pointer to the CPR3 thread
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_init_thread(struct cpr3_thread *thread)
++{
++ int rc;
++
++ rc = cpr3_parse_common_thread_data(thread);
++ if (rc) {
++ cpr3_err(thread->ctrl, "thread %u unable to read CPR thread data from device tree, rc=%d\n",
++ thread->thread_id, rc);
++ return rc;
++ }
++
++ return 0;
++}
++
++/**
++ * cpr4_apss_parse_temp_adj_properties() - parse temperature based
++ * adjustment properties from device tree.
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_parse_temp_adj_properties(struct cpr3_controller *ctrl)
++{
++ struct device_node *of_node = ctrl->dev->of_node;
++ int rc, i, len, temp_point_count;
++
++ if (!of_find_property(of_node, "qcom,cpr-temp-point-map", &len)) {
++ /*
++ * Temperature based adjustments are not defined. Single
++ * temperature band is still valid for per-online-core
++ * adjustments.
++ */
++ ctrl->temp_band_count = 1;
++ return 0;
++ }
++
++ temp_point_count = len / sizeof(u32);
++ if (temp_point_count <= 0 ||
++ temp_point_count > IPQ807x_APSS_MAX_TEMP_POINTS) {
++ cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
++ temp_point_count, IPQ807x_APSS_MAX_TEMP_POINTS);
++ return -EINVAL;
++ }
++
++ ctrl->temp_points = devm_kcalloc(ctrl->dev, temp_point_count,
++ sizeof(*ctrl->temp_points), GFP_KERNEL);
++ if (!ctrl->temp_points)
++ return -ENOMEM;
++
++ rc = of_property_read_u32_array(of_node, "qcom,cpr-temp-point-map",
++ ctrl->temp_points, temp_point_count);
++ if (rc) {
++ cpr3_err(ctrl, "error reading property qcom,cpr-temp-point-map, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ for (i = 0; i < temp_point_count; i++)
++ cpr3_debug(ctrl, "Temperature Point %d=%d\n", i,
++ ctrl->temp_points[i]);
++
++ /*
++ * If t1, t2, and t3 are the temperature points, then the temperature
++ * bands are: (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).
++ */
++ ctrl->temp_band_count = temp_point_count + 1;
++ cpr3_debug(ctrl, "Number of temp bands =%d\n", ctrl->temp_band_count);
++
++ rc = of_property_read_u32(of_node, "qcom,cpr-initial-temp-band",
++ &ctrl->initial_temp_band);
++ if (rc) {
++ cpr3_err(ctrl, "error reading qcom,cpr-initial-temp-band, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (ctrl->initial_temp_band >= ctrl->temp_band_count) {
++ cpr3_err(ctrl, "Initial temperature band value %d should be in range [0 - %d]\n",
++ ctrl->initial_temp_band, ctrl->temp_band_count - 1);
++ return -EINVAL;
++ }
++
++ ctrl->temp_sensor_id_start = IPQ807x_APSS_TEMP_SENSOR_ID_START;
++ ctrl->temp_sensor_id_end = IPQ807x_APSS_TEMP_SENSOR_ID_END;
++ ctrl->allow_temp_adj = true;
++ return rc;
++}
++
++/**
++ * cpr4_apss_parse_boost_properties() - parse configuration data for boost
++ * voltage adjustment for CPR3 regulator from device tree.
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_parse_boost_properties(struct cpr3_regulator *vreg)
++{
++ struct cpr3_controller *ctrl = vreg->thread->ctrl;
++ struct cpr4_ipq807x_apss_fuses *fuse = vreg->platform_fuses;
++ struct cpr3_corner *corner;
++ int i, boost_voltage, final_boost_volt, rc = 0;
++ int *boost_table = NULL, *boost_temp_adj = NULL;
++ int boost_voltage_adjust = 0, boost_num_cores = 0;
++ u32 boost_allowed = 0;
++
++ if (!boost_fuse[fuse->boost_cfg])
++ /* Voltage boost is disabled in fuse */
++ return 0;
++
++ if (of_find_property(vreg->of_node, "qcom,allow-boost", NULL)) {
++ rc = cpr3_parse_array_property(vreg, "qcom,allow-boost", 1,
++ &boost_allowed);
++ if (rc)
++ return rc;
++ }
++
++ if (!boost_allowed) {
++ /* Voltage boost is not enabled for this regulator */
++ return 0;
++ }
++
++ boost_voltage = cpr3_convert_open_loop_voltage_fuse(
++ vreg->cpr4_regulator_data->boost_fuse_ref_volt,
++ vreg->cpr4_regulator_data->fuse_step_volt,
++ fuse->boost_voltage,
++ IPQ807x_APSS_VOLTAGE_FUSE_SIZE);
++
++ /* Log boost voltage value for debugging purposes. */
++ cpr3_info(vreg, "Boost open-loop=%7d uV\n", boost_voltage);
++
++ if (of_find_property(vreg->of_node,
++ "qcom,cpr-boost-voltage-fuse-adjustment", NULL)) {
++ rc = cpr3_parse_array_property(vreg,
++ "qcom,cpr-boost-voltage-fuse-adjustment",
++ 1, &boost_voltage_adjust);
++ if (rc) {
++ cpr3_err(vreg, "qcom,cpr-boost-voltage-fuse-adjustment reading failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ boost_voltage += boost_voltage_adjust;
++ /* Log boost voltage value for debugging purposes. */
++ cpr3_info(vreg, "Adjusted boost open-loop=%7d uV\n",
++ boost_voltage);
++ }
++
++ /* Limit boost voltage value between ceiling and floor voltage limits */
++ boost_voltage = min(boost_voltage, vreg->cpr4_regulator_data->boost_ceiling_volt);
++ boost_voltage = max(boost_voltage, vreg->cpr4_regulator_data->boost_floor_volt);
++
++ /*
++ * The boost feature can only be used for the highest voltage corner.
++ * Also, keep core-count adjustments disabled when the boost feature
++ * is enabled.
++ */
++ corner = &vreg->corner[vreg->corner_count - 1];
++ if (!corner->sdelta) {
++ /*
++ * If core-count/temp adjustments are not defined, the cpr4
++ * sdelta for this corner will not be allocated. Allocate it
++ * here for boost configuration.
++ */
++ corner->sdelta = devm_kzalloc(ctrl->dev,
++ sizeof(*corner->sdelta), GFP_KERNEL);
++ if (!corner->sdelta)
++ return -ENOMEM;
++ }
++ corner->sdelta->temp_band_count = ctrl->temp_band_count;
++
++ rc = of_property_read_u32(vreg->of_node, "qcom,cpr-num-boost-cores",
++ &boost_num_cores);
++ if (rc) {
++ cpr3_err(vreg, "qcom,cpr-num-boost-cores reading failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (boost_num_cores <= 0 ||
++ boost_num_cores > IPQ807x_APSS_CPR_SDELTA_CORE_COUNT) {
++ cpr3_err(vreg, "Invalid boost number of cores = %d\n",
++ boost_num_cores);
++ return -EINVAL;
++ }
++ corner->sdelta->boost_num_cores = boost_num_cores;
++
++ boost_table = devm_kcalloc(ctrl->dev, corner->sdelta->temp_band_count,
++ sizeof(*boost_table), GFP_KERNEL);
++ if (!boost_table)
++ return -ENOMEM;
++
++ if (of_find_property(vreg->of_node,
++ "qcom,cpr-boost-temp-adjustment", NULL)) {
++ boost_temp_adj = kcalloc(corner->sdelta->temp_band_count,
++ sizeof(*boost_temp_adj), GFP_KERNEL);
++ if (!boost_temp_adj)
++ return -ENOMEM;
++
++ rc = cpr3_parse_array_property(vreg,
++ "qcom,cpr-boost-temp-adjustment",
++ corner->sdelta->temp_band_count,
++ boost_temp_adj);
++ if (rc) {
++ cpr3_err(vreg, "qcom,cpr-boost-temp-adjustment reading failed, rc=%d\n",
++ rc);
++ goto done;
++ }
++ }
++
++ for (i = 0; i < corner->sdelta->temp_band_count; i++) {
++ /* Apply static adjustments to boost voltage */
++ final_boost_volt = boost_voltage + (boost_temp_adj == NULL
++ ? 0 : boost_temp_adj[i]);
++ /*
++ * Limit final adjusted boost voltage value between ceiling
++ * and floor voltage limits
++ */
++ final_boost_volt = min(final_boost_volt,
++ vreg->cpr4_regulator_data->boost_ceiling_volt);
++ final_boost_volt = max(final_boost_volt,
++ vreg->cpr4_regulator_data->boost_floor_volt);
++
++ boost_table[i] = (corner->open_loop_volt - final_boost_volt)
++ / ctrl->step_volt;
++ cpr3_debug(vreg, "Adjusted boost voltage margin for temp band %d = %d steps\n",
++ i, boost_table[i]);
++ }
++
++ corner->ceiling_volt = vreg->cpr4_regulator_data->boost_ceiling_volt;
++ corner->sdelta->boost_table = boost_table;
++ corner->sdelta->allow_boost = true;
++ corner->sdelta->allow_core_count_adj = false;
++ vreg->allow_boost = true;
++ ctrl->allow_boost = true;
++done:
++ kfree(boost_temp_adj);
++ return rc;
++}
++
++/**
++ * cpr4_apss_init_regulator() - perform all steps necessary to initialize the
++ * configuration data for a CPR3 regulator
++ * @vreg: Pointer to the CPR3 regulator
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_init_regulator(struct cpr3_regulator *vreg)
++{
++ struct cpr4_ipq807x_apss_fuses *fuse;
++ int rc;
++
++ rc = cpr4_ipq807x_apss_read_fuse_data(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
++ return rc;
++ }
++
++ fuse = vreg->platform_fuses;
++
++ rc = cpr4_apss_parse_corner_data(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to read CPR corner data from device tree, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr3_mem_acc_init(vreg);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(vreg, "unable to initialize mem-acc regulator settings, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr4_ipq807x_apss_calculate_open_loop_voltages(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to calculate open-loop voltages, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr3_limit_open_loop_voltages(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to limit open-loop voltages, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ cpr3_open_loop_voltage_as_ceiling(vreg);
++
++ rc = cpr3_limit_floor_voltages(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to limit floor voltages, rc=%d\n", rc);
++ return rc;
++ }
++
++ rc = cpr4_ipq807x_apss_calculate_target_quotients(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to calculate target quotients, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr4_parse_core_count_temp_voltage_adj(vreg, false);
++ if (rc) {
++ cpr3_err(vreg, "unable to parse temperature and core count voltage adjustments, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (vreg->allow_core_count_adj && (vreg->max_core_count <= 0
++ || vreg->max_core_count >
++ IPQ807x_APSS_CPR_SDELTA_CORE_COUNT)) {
++ cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
++ vreg->max_core_count);
++ return -EINVAL;
++ }
++
++ rc = cpr4_apss_parse_boost_properties(vreg);
++ if (rc) {
++ cpr3_err(vreg, "unable to parse boost adjustments, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ cpr4_apss_print_settings(vreg);
++
++ return rc;
++}
++
++/**
++ * cpr4_apss_init_controller() - perform APSS CPR4 controller specific
++ * initializations
++ * @ctrl: Pointer to the CPR3 controller
++ *
++ * Return: 0 on success, errno on failure
++ */
++static int cpr4_apss_init_controller(struct cpr3_controller *ctrl)
++{
++ int rc;
++
++ rc = cpr3_parse_common_ctrl_data(ctrl);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable to parse common controller data, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = of_property_read_u32(ctrl->dev->of_node,
++ "qcom,cpr-down-error-step-limit",
++ &ctrl->down_error_step_limit);
++ if (rc) {
++ cpr3_err(ctrl, "error reading qcom,cpr-down-error-step-limit, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = of_property_read_u32(ctrl->dev->of_node,
++ "qcom,cpr-up-error-step-limit",
++ &ctrl->up_error_step_limit);
++ if (rc) {
++ cpr3_err(ctrl, "error reading qcom,cpr-up-error-step-limit, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ /*
++ * Use fixed step quotient if specified otherwise use dynamic
++ * calculated per RO step quotient
++ */
++ of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-step-quot-fixed",
++ &ctrl->step_quot_fixed);
++ ctrl->use_dynamic_step_quot = ctrl->step_quot_fixed ? false : true;
++
++ ctrl->saw_use_unit_mV = of_property_read_bool(ctrl->dev->of_node,
++ "qcom,cpr-saw-use-unit-mV");
++
++ of_property_read_u32(ctrl->dev->of_node,
++ "qcom,cpr-voltage-settling-time",
++ &ctrl->voltage_settling_time);
++
++ if (of_find_property(ctrl->dev->of_node, "vdd-limit-supply", NULL)) {
++ ctrl->vdd_limit_regulator =
++ devm_regulator_get(ctrl->dev, "vdd-limit");
++ if (IS_ERR(ctrl->vdd_limit_regulator)) {
++ rc = PTR_ERR(ctrl->vdd_limit_regulator);
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable to request vdd-limit regulator, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ rc = cpr3_apm_init(ctrl);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "unable to initialize APM settings, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr4_apss_parse_temp_adj_properties(ctrl);
++ if (rc) {
++ cpr3_err(ctrl, "unable to parse temperature adjustment properties, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ ctrl->sensor_count = IPQ807x_APSS_CPR_SENSOR_COUNT;
++
++ /*
++ * APSS only has one thread (0) per controller so the zeroed
++ * array does not need further modification.
++ */
++ ctrl->sensor_owner = devm_kcalloc(ctrl->dev, ctrl->sensor_count,
++ sizeof(*ctrl->sensor_owner), GFP_KERNEL);
++ if (!ctrl->sensor_owner)
++ return -ENOMEM;
++
++ ctrl->ctrl_type = CPR_CTRL_TYPE_CPR4;
++ ctrl->supports_hw_closed_loop = false;
++ ctrl->use_hw_closed_loop = of_property_read_bool(ctrl->dev->of_node,
++ "qcom,cpr-hw-closed-loop");
++ return 0;
++}
++
++static int cpr4_apss_regulator_suspend(struct platform_device *pdev,
++ pm_message_t state)
++{
++ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
++
++ return cpr3_regulator_suspend(ctrl);
++}
++
++static int cpr4_apss_regulator_resume(struct platform_device *pdev)
++{
++ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
++
++ return cpr3_regulator_resume(ctrl);
++}
++
++static void ipq6018_set_mem_acc(struct regulator_dev *rdev)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++
++ ipq6018_mem_acc_tcsr[0].ioremap_addr =
++ ioremap(ipq6018_mem_acc_tcsr[0].phy_addr, 0x4);
++ ipq6018_mem_acc_tcsr[1].ioremap_addr =
++ ioremap(ipq6018_mem_acc_tcsr[1].phy_addr, 0x4);
++
++ if ((ipq6018_mem_acc_tcsr[0].ioremap_addr != NULL) &&
++ (ipq6018_mem_acc_tcsr[1].ioremap_addr != NULL) &&
++ (vreg->current_corner == (vreg->corner_count - CPR3_CORNER_OFFSET))) {
++
++ writel_relaxed(ipq6018_mem_acc_tcsr[0].value,
++ ipq6018_mem_acc_tcsr[0].ioremap_addr);
++ writel_relaxed(ipq6018_mem_acc_tcsr[1].value,
++ ipq6018_mem_acc_tcsr[1].ioremap_addr);
++ }
++}
++
++static void ipq6018_clr_mem_acc(struct regulator_dev *rdev)
++{
++ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
++
++ if ((ipq6018_mem_acc_tcsr[0].ioremap_addr != NULL) &&
++ (ipq6018_mem_acc_tcsr[1].ioremap_addr != NULL) &&
++ (vreg->current_corner != vreg->corner_count - CPR3_CORNER_OFFSET)) {
++ writel_relaxed(0x0, ipq6018_mem_acc_tcsr[0].ioremap_addr);
++ writel_relaxed(0x0, ipq6018_mem_acc_tcsr[1].ioremap_addr);
++ }
++
++ iounmap(ipq6018_mem_acc_tcsr[0].ioremap_addr);
++ iounmap(ipq6018_mem_acc_tcsr[1].ioremap_addr);
++}
++
++static struct cpr4_mem_acc_func ipq6018_mem_acc_funcs = {
++ .set_mem_acc = ipq6018_set_mem_acc,
++ .clear_mem_acc = ipq6018_clr_mem_acc
++};
++
++static const struct cpr4_reg_data ipq807x_cpr_apss = {
++ .cpr_valid_fuse_count = IPQ807x_APSS_FUSE_CORNERS,
++ .fuse_ref_volt = ipq807x_apss_fuse_ref_volt,
++ .fuse_step_volt = IPQ807x_APSS_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ807x_APSS_CPR_CLOCK_RATE,
++ .boost_fuse_ref_volt= IPQ807x_APSS_BOOST_FUSE_REF_VOLT,
++ .boost_ceiling_volt= IPQ807x_APSS_BOOST_CEILING_VOLT,
++ .boost_floor_volt= IPQ807x_APSS_BOOST_FLOOR_VOLT,
++ .cpr3_fuse_params = &ipq807x_fuse_params,
++ .mem_acc_funcs = NULL,
++};
++
++static const struct cpr4_reg_data ipq817x_cpr_apss = {
++ .cpr_valid_fuse_count = IPQ817x_APPS_FUSE_CORNERS,
++ .fuse_ref_volt = ipq807x_apss_fuse_ref_volt,
++ .fuse_step_volt = IPQ807x_APSS_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ807x_APSS_CPR_CLOCK_RATE,
++ .boost_fuse_ref_volt= IPQ807x_APSS_BOOST_FUSE_REF_VOLT,
++ .boost_ceiling_volt= IPQ807x_APSS_BOOST_CEILING_VOLT,
++ .boost_floor_volt= IPQ807x_APSS_BOOST_FLOOR_VOLT,
++ .cpr3_fuse_params = &ipq807x_fuse_params,
++ .mem_acc_funcs = NULL,
++};
++
++static const struct cpr4_reg_data ipq6018_cpr_apss = {
++ .cpr_valid_fuse_count = IPQ6018_APSS_FUSE_CORNERS,
++ .fuse_ref_volt = ipq6018_apss_fuse_ref_volt,
++ .fuse_step_volt = IPQ6018_APSS_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ6018_APSS_CPR_CLOCK_RATE,
++ .boost_fuse_ref_volt = IPQ6018_APSS_BOOST_FUSE_REF_VOLT,
++ .boost_ceiling_volt = IPQ6018_APSS_BOOST_CEILING_VOLT,
++ .boost_floor_volt = IPQ6018_APSS_BOOST_FLOOR_VOLT,
++ .cpr3_fuse_params = &ipq6018_fuse_params,
++ .mem_acc_funcs = &ipq6018_mem_acc_funcs,
++};
++
++static const struct cpr4_reg_data ipq9574_cpr_apss = {
++ .cpr_valid_fuse_count = IPQ9574_APSS_FUSE_CORNERS,
++ .fuse_ref_volt = ipq9574_apss_fuse_ref_volt,
++ .fuse_step_volt = IPQ9574_APSS_FUSE_STEP_VOLT,
++ .cpr_clk_rate = IPQ6018_APSS_CPR_CLOCK_RATE,
++ .boost_fuse_ref_volt = IPQ6018_APSS_BOOST_FUSE_REF_VOLT,
++ .boost_ceiling_volt = IPQ6018_APSS_BOOST_CEILING_VOLT,
++ .boost_floor_volt = IPQ6018_APSS_BOOST_FLOOR_VOLT,
++ .cpr3_fuse_params = &ipq9574_fuse_params,
++ .mem_acc_funcs = NULL,
++};
++
++static struct of_device_id cpr4_regulator_match_table[] = {
++ {
++ .compatible = "qcom,cpr4-ipq807x-apss-regulator",
++ .data = &ipq807x_cpr_apss
++ },
++ {
++ .compatible = "qcom,cpr4-ipq817x-apss-regulator",
++ .data = &ipq817x_cpr_apss
++ },
++ {
++ .compatible = "qcom,cpr4-ipq6018-apss-regulator",
++ .data = &ipq6018_cpr_apss
++ },
++ {
++ .compatible = "qcom,cpr4-ipq9574-apss-regulator",
++ .data = &ipq9574_cpr_apss
++ },
++ {}
++};
++
++static int cpr4_apss_regulator_probe(struct platform_device *pdev)
++{
++ struct device *dev = &pdev->dev;
++ struct cpr3_controller *ctrl;
++ const struct of_device_id *match;
++ struct cpr4_reg_data *cpr_data;
++ int i, rc;
++
++ if (!dev->of_node) {
++ dev_err(dev, "Device tree node is missing\n");
++ return -EINVAL;
++ }
++
++ ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
++ if (!ctrl)
++ return -ENOMEM;
++
++ match = of_match_device(cpr4_regulator_match_table, &pdev->dev);
++ if (!match)
++ return -ENODEV;
++
++ cpr_data = (struct cpr4_reg_data *)match->data;
++ g_valid_fuse_count = cpr_data->cpr_valid_fuse_count;
++ dev_info(dev, "CPR valid fuse count: %d\n", g_valid_fuse_count);
++ ctrl->cpr_clock_rate = cpr_data->cpr_clk_rate;
++
++ ctrl->dev = dev;
++ /* Set to false later if anything precludes CPR operation. */
++ ctrl->cpr_allowed_hw = true;
++
++ rc = of_property_read_string(dev->of_node, "qcom,cpr-ctrl-name",
++ &ctrl->name);
++ if (rc) {
++ cpr3_err(ctrl, "unable to read qcom,cpr-ctrl-name, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr3_map_fuse_base(ctrl, pdev);
++ if (rc) {
++ cpr3_err(ctrl, "could not map fuse base address\n");
++ return rc;
++ }
++
++ rc = cpr3_read_tcsr_setting(ctrl, pdev, IPQ807x_APSS_CPR_TCSR_START,
++ IPQ807x_APSS_CPR_TCSR_END);
++ if (rc) {
++ cpr3_err(ctrl, "could not read CPR tcsr setting\n");
++ return rc;
++ }
++
++ rc = cpr3_allocate_threads(ctrl, 0, 0);
++ if (rc) {
++ cpr3_err(ctrl, "failed to allocate CPR thread array, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ if (ctrl->thread_count != 1) {
++ cpr3_err(ctrl, "expected 1 thread but found %d\n",
++ ctrl->thread_count);
++ return -EINVAL;
++ }
++
++ rc = cpr4_apss_init_controller(ctrl);
++ if (rc) {
++ if (rc != -EPROBE_DEFER)
++ cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
++ rc);
++ return rc;
++ }
++
++ rc = cpr4_apss_init_thread(&ctrl->thread[0]);
++ if (rc) {
++ cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
++ return rc;
++ }
++
++ for (i = 0; i < ctrl->thread[0].vreg_count; i++) {
++ ctrl->thread[0].vreg[i].cpr4_regulator_data = cpr_data;
++ rc = cpr4_apss_init_regulator(&ctrl->thread[0].vreg[i]);
++ if (rc) {
++ cpr3_err(&ctrl->thread[0].vreg[i], "regulator initialization failed, rc=%d\n",
++ rc);
++ return rc;
++ }
++ }
++
++ platform_set_drvdata(pdev, ctrl);
++
++ return cpr3_regulator_register(pdev, ctrl);
++}
++
++static int cpr4_apss_regulator_remove(struct platform_device *pdev)
++{
++ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
++
++ return cpr3_regulator_unregister(ctrl);
++}
++
++static struct platform_driver cpr4_apss_regulator_driver = {
++ .driver = {
++ .name = "qcom,cpr4-apss-regulator",
++ .of_match_table = cpr4_regulator_match_table,
++ .owner = THIS_MODULE,
++ },
++ .probe = cpr4_apss_regulator_probe,
++ .remove = cpr4_apss_regulator_remove,
++ .suspend = cpr4_apss_regulator_suspend,
++ .resume = cpr4_apss_regulator_resume,
++};
++
++static int cpr4_regulator_init(void)
++{
++ return platform_driver_register(&cpr4_apss_regulator_driver);
++}
++
++static void cpr4_regulator_exit(void)
++{
++ platform_driver_unregister(&cpr4_apss_regulator_driver);
++}
++
++MODULE_DESCRIPTION("CPR4 APSS regulator driver");
++MODULE_LICENSE("GPL v2");
++
++arch_initcall(cpr4_regulator_init);
++module_exit(cpr4_regulator_exit);
+--- /dev/null
++++ b/include/soc/qcom/socinfo.h
+@@ -0,0 +1,463 @@
++/* Copyright (c) 2009-2014, 2016, 2020, The Linux Foundation. All rights reserved.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 and
++ * only version 2 as published by the Free Software Foundation.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ */
++
++#ifndef _ARCH_ARM_MACH_MSM_SOCINFO_H_
++#define _ARCH_ARM_MACH_MSM_SOCINFO_H_
++
++#include <linux/of.h>
++
++#define CPU_IPQ8074 323
++#define CPU_IPQ8072 342
++#define CPU_IPQ8076 343
++#define CPU_IPQ8078 344
++#define CPU_IPQ8070 375
++#define CPU_IPQ8071 376
++
++#define CPU_IPQ8072A 389
++#define CPU_IPQ8074A 390
++#define CPU_IPQ8076A 391
++#define CPU_IPQ8078A 392
++#define CPU_IPQ8070A 395
++#define CPU_IPQ8071A 396
++
++#define CPU_IPQ8172 397
++#define CPU_IPQ8173 398
++#define CPU_IPQ8174 399
++
++#define CPU_IPQ6018 402
++#define CPU_IPQ6028 403
++#define CPU_IPQ6000 421
++#define CPU_IPQ6010 422
++#define CPU_IPQ6005 453
++
++#define CPU_IPQ5010 446
++#define CPU_IPQ5018 447
++#define CPU_IPQ5028 448
++#define CPU_IPQ5000 503
++#define CPU_IPQ0509 504
++#define CPU_IPQ0518 505
++
++#define CPU_IPQ9514 510
++#define CPU_IPQ9554 512
++#define CPU_IPQ9570 513
++#define CPU_IPQ9574 514
++#define CPU_IPQ9550 511
++#define CPU_IPQ9510 521
++
++static inline int read_ipq_soc_version_major(void)
++{
++ const int *prop;
++ prop = of_get_property(of_find_node_by_path("/"), "soc_version_major",
++ NULL);
++
++ if (!prop)
++ return -EINVAL;
++
++ return le32_to_cpu(*prop);
++}
++
++static inline int read_ipq_cpu_type(void)
++{
++ const int *prop;
++ prop = of_get_property(of_find_node_by_path("/"), "cpu_type", NULL);
++ /*
++ * Return Default CPU type if "cpu_type" property is not found in DTSI
++ */
++ if (!prop)
++ return CPU_IPQ8074;
++
++ return le32_to_cpu(*prop);
++}
++
++static inline int cpu_is_ipq8070(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8070;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8071(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8071;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8072(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8072;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8074(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8074;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8076(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8076;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8078(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8078;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8072a(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8072A;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8074a(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8074A;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8076a(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8076A;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8078a(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8078A;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8070a(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8070A;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8071a(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8071A;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8172(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8172;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8173(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8173;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq8174(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ8174;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq6018(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ6018;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq6028(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ6028;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq6000(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ6000;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq6010(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ6010;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq6005(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ6005;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq5010(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ5010;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq5018(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ5018;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq5028(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ5028;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq5000(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ5000;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq0509(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ0509;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq0518(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ0518;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq9514(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ9514;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq9554(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ9554;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq9570(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ9570;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq9574(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ9574;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq9550(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ9550;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq9510(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return read_ipq_cpu_type() == CPU_IPQ9510;
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq807x(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq8072() || cpu_is_ipq8074() ||
++ cpu_is_ipq8076() || cpu_is_ipq8078() ||
++ cpu_is_ipq8070() || cpu_is_ipq8071() ||
++ cpu_is_ipq8072a() || cpu_is_ipq8074a() ||
++ cpu_is_ipq8076a() || cpu_is_ipq8078a() ||
++ cpu_is_ipq8070a() || cpu_is_ipq8071a() ||
++ cpu_is_ipq8172() || cpu_is_ipq8173() ||
++ cpu_is_ipq8174();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq60xx(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq6018() || cpu_is_ipq6028() ||
++ cpu_is_ipq6000() || cpu_is_ipq6010() ||
++ cpu_is_ipq6005();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq50xx(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq5010() || cpu_is_ipq5018() ||
++ cpu_is_ipq5028() || cpu_is_ipq5000() ||
++ cpu_is_ipq0509() || cpu_is_ipq0518();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_ipq95xx(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq9514() || cpu_is_ipq9554() ||
++ cpu_is_ipq9570() || cpu_is_ipq9574() ||
++ cpu_is_ipq9550() || cpu_is_ipq9510();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_nss_crypto_enabled(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq807x() || cpu_is_ipq60xx() ||
++ cpu_is_ipq50xx() || cpu_is_ipq9570() ||
++ cpu_is_ipq9550() || cpu_is_ipq9574() ||
++ cpu_is_ipq9554();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_internal_wifi_enabled(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq807x() || cpu_is_ipq60xx() ||
++ cpu_is_ipq50xx() || cpu_is_ipq9514() ||
++ cpu_is_ipq9554() || cpu_is_ipq9574();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_uniphy1_enabled(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq807x() || cpu_is_ipq60xx() ||
++ cpu_is_ipq9554() || cpu_is_ipq9570() ||
++ cpu_is_ipq9574() || cpu_is_ipq9550();
++#else
++ return 0;
++#endif
++}
++
++static inline int cpu_is_uniphy2_enabled(void)
++{
++#ifdef CONFIG_ARCH_QCOM
++ return cpu_is_ipq807x() || cpu_is_ipq9570() ||
++ cpu_is_ipq9574();
++#else
++ return 0;
++#endif
++}
++
++#endif /* _ARCH_ARM_MACH_MSM_SOCINFO_H_ */
diff --git a/target/linux/qualcommax/patches-6.1/0902-arm64-dts-ipq8074-add-label-to-clocks.patch b/target/linux/qualcommax/patches-6.1/0902-arm64-dts-ipq8074-add-label-to-clocks.patch
new file mode 100644
index 0000000000..9b8b4df12b
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0902-arm64-dts-ipq8074-add-label-to-clocks.patch
@@ -0,0 +1,24 @@
+From 6baf7e4abcea6f7ac21eccf072a20078b39d064c Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Wed, 9 Feb 2022 23:13:26 +0100
+Subject: [PATCH] arm64: dts: ipq8074: add label to clocks
+
+Add label to clocks node as that makes it easy to add the NSS fixed
+clocks that are required in their DTSI.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ arch/arm64/boot/dts/qcom/ipq8074.dtsi | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi
+@@ -15,7 +15,7 @@
+ compatible = "qcom,ipq8074";
+ interrupt-parent = <&intc>;
+
+- clocks {
++ clocks: clocks {
+ sleep_clk: sleep_clk {
+ compatible = "fixed-clock";
+ clock-frequency = <32768>;
diff --git a/target/linux/qualcommax/patches-6.1/0903-psci-dont-advertise-OSI-support-for-IPQ6018.patch b/target/linux/qualcommax/patches-6.1/0903-psci-dont-advertise-OSI-support-for-IPQ6018.patch
new file mode 100644
index 0000000000..5fcb90098f
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0903-psci-dont-advertise-OSI-support-for-IPQ6018.patch
@@ -0,0 +1,40 @@
+From 563db68137475d011b355bfe674d1b7a24778091 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Sat, 8 Oct 2022 22:26:31 +0200
+Subject: [PATCH] psci: dont advertise OSI support for IPQ6018
+
+Some older IPQ60xx SoC series boards ship with TrustZone/QSEE firmware
+older than TZ.WNS.5.1-00084 which will advertise OSI[1] but are broken
+and trying to use OSI will cause the board to hang until WDT kicks in.
+
+So workaround it by checking for SoC compatible and returning false so
+OSI is not used.
+
+[1] https://www.spinics.net/lists/linux-arm-msm/msg79916.html
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/firmware/psci/psci.c | 12 ++++++++++++
+ 1 file changed, 12 insertions(+)
+
+--- a/drivers/firmware/psci/psci.c
++++ b/drivers/firmware/psci/psci.c
+@@ -87,6 +87,18 @@ static inline bool psci_has_ext_power_st
+
+ bool psci_has_osi_support(void)
+ {
++ /*
++ * Some older IPQ60xx SoC series boards ship with
++ * TrustZone/QSEE firmware older than TZ.WNS.5.1-00084
++ * which will advertise OSI but is broken and trying
++ * to use OSI will cause the board to hang until WDT
++ * kicks in.
++ * So workaround it by checking for SoC compatible
++ * and returning false so OSI is not used.
++ */
++ if (of_machine_is_compatible("qcom,ipq6018"))
++ return false;
++
+ return psci_cpu_suspend_feature & PSCI_1_0_OS_INITIATED;
+ }
+
diff --git a/target/linux/qualcommax/patches-6.1/0904-clk-qcom-ipq6018-workaround-networking-clock-parenti.patch b/target/linux/qualcommax/patches-6.1/0904-clk-qcom-ipq6018-workaround-networking-clock-parenti.patch
new file mode 100644
index 0000000000..175d475849
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0904-clk-qcom-ipq6018-workaround-networking-clock-parenti.patch
@@ -0,0 +1,109 @@
+From 0c5b5243ad55ae744e790ba90c5ad37a93bd1377 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robimarko@gmail.com>
+Date: Tue, 11 Oct 2022 23:38:45 +0200
+Subject: [PATCH] clk: qcom: ipq6018: workaround networking clock parenting
+
+Currently, networking clocks are only looked up by fw_name however,
+these are registered and setup by SSDK and are not available to the
+GCC driver at all, so work around that by providing a global name
+fallback.
+
+While we are here, provide global fallback for bias_pll_cc_clk and
+bias_pll_nss_noc_clk as well as these are fixed clocks also not available
+to the driver.
+
+Signed-off-by: Robert Marko <robimarko@gmail.com>
+---
+ drivers/clk/qcom/gcc-ipq6018.c | 39 +++++++++++++++++-----------------
+ 1 file changed, 19 insertions(+), 20 deletions(-)
+
+--- a/drivers/clk/qcom/gcc-ipq6018.c
++++ b/drivers/clk/qcom/gcc-ipq6018.c
+@@ -361,7 +361,7 @@ static const struct freq_tbl ftbl_nss_pp
+
+ static const struct clk_parent_data gcc_xo_bias_gpll0_gpll4_nss_ubi32[] = {
+ { .fw_name = "xo" },
+- { .fw_name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ { .hw = &gpll0.clkr.hw },
+ { .hw = &gpll4.clkr.hw },
+ { .hw = &nss_crypto_pll.clkr.hw },
+@@ -527,12 +527,12 @@ static const struct freq_tbl ftbl_nss_po
+ static const struct clk_parent_data
+ gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias[] = {
+ { .fw_name = "xo" },
+- { .fw_name = "uniphy0_gcc_rx_clk" },
+- { .fw_name = "uniphy0_gcc_tx_clk" },
+- { .fw_name = "uniphy1_gcc_rx_clk" },
+- { .fw_name = "uniphy1_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy1_gcc_rx_clk", .name = "uniphy1_gcc_rx_clk" },
++ { .fw_name = "uniphy1_gcc_tx_clk", .name = "uniphy1_gcc_tx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .fw_name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map
+@@ -574,12 +574,12 @@ static const struct freq_tbl ftbl_nss_po
+ static const struct clk_parent_data
+ gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias[] = {
+ { .fw_name = "xo" },
+- { .fw_name = "uniphy0_gcc_tx_clk" },
+- { .fw_name = "uniphy0_gcc_rx_clk" },
+- { .fw_name = "uniphy1_gcc_tx_clk" },
+- { .fw_name = "uniphy1_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy1_gcc_tx_clk", .name = "uniphy1_gcc_tx_clk" },
++ { .fw_name = "uniphy1_gcc_rx_clk", .name = "uniphy1_gcc_rx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .fw_name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map
+@@ -715,10 +715,10 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy0_rx_tx_ubi32_bias[] = {
+ { .fw_name = "xo" },
+- { .fw_name = "uniphy0_gcc_rx_clk" },
+- { .fw_name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .fw_name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_uniphy0_rx_tx_ubi32_bias_map[] = {
+@@ -751,10 +751,10 @@ static const struct freq_tbl ftbl_nss_po
+
+ static const struct clk_parent_data gcc_xo_uniphy0_tx_rx_ubi32_bias[] = {
+ { .fw_name = "xo" },
+- { .fw_name = "uniphy0_gcc_tx_clk" },
+- { .fw_name = "uniphy0_gcc_rx_clk" },
++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" },
++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" },
+ { .hw = &ubi32_pll.clkr.hw },
+- { .fw_name = "bias_pll_cc_clk" },
++ { .fw_name = "bias_pll_cc_clk", .name = "bias_pll_cc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_uniphy0_tx_rx_ubi32_bias_map[] = {
+@@ -1898,12 +1898,11 @@ static const struct freq_tbl ftbl_ubi32_
+ { }
+ };
+
+-static const struct clk_parent_data
+- gcc_xo_gpll0_gpll2_bias_pll_nss_noc_clk[] = {
++static const struct clk_parent_data gcc_xo_gpll0_gpll2_bias_pll_nss_noc_clk[] = {
+ { .fw_name = "xo" },
+ { .hw = &gpll0.clkr.hw },
+ { .hw = &gpll2.clkr.hw },
+- { .fw_name = "bias_pll_nss_noc_clk" },
++ { .fw_name = "bias_pll_nss_noc_clk", .name = "bias_pll_nss_noc_clk" },
+ };
+
+ static const struct parent_map gcc_xo_gpll0_gpll2_bias_pll_nss_noc_clk_map[] = {
diff --git a/target/linux/qualcommax/patches-6.1/0905-remoteproc-q6v5_wcss-change-ssr-name-for-ipq6018-wif.patch b/target/linux/qualcommax/patches-6.1/0905-remoteproc-q6v5_wcss-change-ssr-name-for-ipq6018-wif.patch
new file mode 100644
index 0000000000..58bea43892
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0905-remoteproc-q6v5_wcss-change-ssr-name-for-ipq6018-wif.patch
@@ -0,0 +1,40 @@
+From 505f9c8653fc218ca47a153ec58ebc16bef5502f Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 16 Jan 2024 10:42:40 +0200
+Subject: [PATCH 16/19] remoteproc: q6v5_wcss: change ssr name for ipq6018 wifi
+ subsystem
+
+On IPQ6018 this string ends up being sent to RPM when remoteproc stops
+(on crash or rmmod ath11k). "q6wcss" is not a valid name (not found by
+`strings` in rpm.mbn), so this causes RPM do 'something' (presumably crash)
+causing a system reboot followed by hang in XBL, with no WDT running.
+Let's change ssr_name to a more sensible 'wcnss', that does not cause such
+issues.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -1143,8 +1143,8 @@ static int q6v5_wcss_probe(struct platfo
+ if (ret)
+ goto free_rproc;
+
+- qcom_add_glink_subdev(rproc, &wcss->glink_subdev, "q6wcss");
+- qcom_add_ssr_subdev(rproc, &wcss->ssr_subdev, "q6wcss");
++ qcom_add_glink_subdev(rproc, &wcss->glink_subdev, desc->ssr_name);
++ qcom_add_ssr_subdev(rproc, &wcss->ssr_subdev, desc->ssr_name);
+
+ if (desc->ssctl_id)
+ wcss->sysmon = qcom_add_sysmon_subdev(rproc,
+@@ -1201,7 +1201,7 @@ static const struct wcss_data wcss_ipq60
+ .aon_reset_required = true,
+ .wcss_q6_reset_required = true,
+ .bcr_reset_required = false,
+- .ssr_name = "q6wcss",
++ .ssr_name = "wcnss",
+ .ops = &q6v5_wcss_ipq8074_ops,
+ .requires_force_stop = true,
+ .need_mem_protection = true,
diff --git a/target/linux/qualcommax/patches-6.1/0906-arm64-dts-qcom-ipq6018-add-wifi-node.patch b/target/linux/qualcommax/patches-6.1/0906-arm64-dts-qcom-ipq6018-add-wifi-node.patch
new file mode 100644
index 0000000000..247df110ff
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0906-arm64-dts-qcom-ipq6018-add-wifi-node.patch
@@ -0,0 +1,120 @@
+From 153c74fc80b9f33ed1a50d7790bf6979fdceb370 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 16 Jan 2024 11:41:06 +0200
+Subject: [PATCH 19/19] arm64: dts: qcom: ipq6018: add wifi node
+
+IPQ6018 has a AHB based Q6v5 802.11ax radios that are supported
+by the ath11k.
+
+Add the required DT node to enable the built-in radios.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 96 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 96 insertions(+)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -807,6 +807,102 @@
+ };
+ };
+
++ wifi: wifi@c000000 {
++ compatible = "qcom,ipq6018-wifi";
++ reg = <0x0 0xc000000 0x0 0x1000000>;
++ qcom,rproc = <&q6v5_wcss>;
++ interrupts = <GIC_SPI 320 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 319 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 318 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 317 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 316 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 315 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 314 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 311 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 310 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 411 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 410 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 40 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 39 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 302 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 301 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 37 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 36 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 296 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 295 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 294 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 293 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 292 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 291 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 290 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 289 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 288 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 239 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 236 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 235 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 234 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 233 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 232 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 231 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 230 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 229 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 228 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 224 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 223 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 203 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 183 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 180 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 179 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 178 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 177 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 176 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 163 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 160 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 159 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 158 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 157 IRQ_TYPE_EDGE_RISING>,
++ <GIC_SPI 156 IRQ_TYPE_EDGE_RISING>;
++ interrupt-names = "misc-pulse1", "misc-latch", "sw-exception",
++ "watchdog", "ce0", "ce1", "ce2", "ce3", "ce4",
++ "ce5", "ce6", "ce7", "ce8", "ce9", "ce10",
++ "ce11", "host2wbm-desc-feed",
++ "host2reo-re-injection", "host2reo-command",
++ "host2rxdma-monitor-ring3",
++ "host2rxdma-monitor-ring2",
++ "host2rxdma-monitor-ring1",
++ "reo2ost-exception", "wbm2host-rx-release",
++ "reo2host-status",
++ "reo2host-destination-ring4",
++ "reo2host-destination-ring3",
++ "reo2host-destination-ring2",
++ "reo2host-destination-ring1",
++ "rxdma2host-monitor-destination-mac3",
++ "rxdma2host-monitor-destination-mac2",
++ "rxdma2host-monitor-destination-mac1",
++ "ppdu-end-interrupts-mac3",
++ "ppdu-end-interrupts-mac2",
++ "ppdu-end-interrupts-mac1",
++ "rxdma2host-monitor-status-ring-mac3",
++ "rxdma2host-monitor-status-ring-mac2",
++ "rxdma2host-monitor-status-ring-mac1",
++ "host2rxdma-host-buf-ring-mac3",
++ "host2rxdma-host-buf-ring-mac2",
++ "host2rxdma-host-buf-ring-mac1",
++ "rxdma2host-destination-ring-mac3",
++ "rxdma2host-destination-ring-mac2",
++ "rxdma2host-destination-ring-mac1",
++ "host2tcl-input-ring4",
++ "host2tcl-input-ring3",
++ "host2tcl-input-ring2",
++ "host2tcl-input-ring1",
++ "wbm2host-tx-completions-ring3",
++ "wbm2host-tx-completions-ring2",
++ "wbm2host-tx-completions-ring1",
++ "tcl2host-status-ring";
++ status = "disabled";
++ };
++
+ q6v5_wcss: remoteproc@cd00000 {
+ compatible = "qcom,ipq6018-wcss-pil";
+ reg = <0x0 0x0cd00000 0x0 0x4040>,
diff --git a/target/linux/qualcommax/patches-6.1/0907-soc-qcom-fix-smp2p-ack-on-ipq6018.patch b/target/linux/qualcommax/patches-6.1/0907-soc-qcom-fix-smp2p-ack-on-ipq6018.patch
new file mode 100644
index 0000000000..88e294562d
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0907-soc-qcom-fix-smp2p-ack-on-ipq6018.patch
@@ -0,0 +1,53 @@
+From d93936f175bd914067df8f63f5fbe6e3b77bb4d2 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 23 May 2023 14:46:28 +0300
+Subject: [PATCH 11/19] soc: qcom: fix smp2p ack on ipq6018
+
+IPQ6018 seem to need different ack mechanism for smp2p messaging. This
+fixes q6v5_wcss remoteproc firmware reloading. Without this first load
+is OK, but subsequent loads would hang and fail to complete.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 1 +
+ drivers/soc/qcom/smp2p.c | 6 +++++-
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -1155,6 +1155,7 @@
+
+ wcss_smp2p_out: master-kernel {
+ qcom,entry-name = "master-kernel";
++ qcom,smp2p-feature-ssr-ack;
+ #qcom,smem-state-cells = <1>;
+ };
+
+--- a/drivers/soc/qcom/smp2p.c
++++ b/drivers/soc/qcom/smp2p.c
+@@ -158,6 +158,8 @@ struct qcom_smp2p {
+
+ struct list_head inbound;
+ struct list_head outbound;
++
++ bool need_ssr_ack;
+ };
+
+ static void qcom_smp2p_kick(struct qcom_smp2p *smp2p)
+@@ -306,7 +308,7 @@ static irqreturn_t qcom_smp2p_intr(int i
+ ack_restart = qcom_smp2p_check_ssr(smp2p);
+ qcom_smp2p_notify_in(smp2p);
+
+- if (ack_restart)
++ if (ack_restart || smp2p->need_ssr_ack)
+ qcom_smp2p_do_ssr_ack(smp2p);
+ }
+
+@@ -427,6 +429,7 @@ static int qcom_smp2p_outbound_entry(str
+
+ /* Make the logical entry reference the physical value */
+ entry->value = &out->entries[out->valid_entries].value;
++ smp2p->need_ssr_ack = of_property_read_bool(node, "qcom,smp2p-feature-ssr-ack");
+
+ out->valid_entries++;
+
diff --git a/target/linux/qualcommax/patches-6.1/0908-remoteproc-qcom_q6v5_wcss-add-optional-qdss_at-clock.patch b/target/linux/qualcommax/patches-6.1/0908-remoteproc-qcom_q6v5_wcss-add-optional-qdss_at-clock.patch
new file mode 100644
index 0000000000..cfc9adda04
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0908-remoteproc-qcom_q6v5_wcss-add-optional-qdss_at-clock.patch
@@ -0,0 +1,55 @@
+From 87dbcc69a7e3fe6ccddf4fe9bdbf51330f5e4a77 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 23 Jan 2024 11:04:04 +0200
+Subject: [PATCH] remoteproc: qcom_q6v5_wcss: add optional qdss_at clock
+
+IPQ6018 needs QDSS_AT clock enabled when loading wifi. Optionally enable it
+when provided by DT.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ drivers/remoteproc/qcom_q6v5_wcss.c | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+--- a/drivers/remoteproc/qcom_q6v5_wcss.c
++++ b/drivers/remoteproc/qcom_q6v5_wcss.c
+@@ -120,6 +120,7 @@ struct q6v5_wcss {
+ struct clk *qdsp6ss_core_gfmux;
+ struct clk *lcc_bcr_sleep;
+ struct clk *prng_clk;
++ struct clk *qdss_clk;
+ struct regulator *cx_supply;
+ struct qcom_sysmon *sysmon;
+
+@@ -259,6 +260,9 @@ static int q6v5_wcss_start(struct rproc
+ return ret;
+ }
+
++ if (wcss->qdss_clk)
++ clk_prepare_enable(wcss->qdss_clk);
++
+ qcom_q6v5_prepare(&wcss->q6v5);
+
+ if (wcss->need_mem_protection) {
+@@ -772,6 +776,8 @@ static int q6v5_wcss_stop(struct rproc *
+ }
+
+ pas_done:
++ if (wcss->qdss_clk)
++ clk_disable_unprepare(wcss->qdss_clk);
+ clk_disable_unprepare(wcss->prng_clk);
+ qcom_q6v5_unprepare(&wcss->q6v5);
+
+@@ -981,6 +987,12 @@ static int ipq_init_clock(struct q6v5_wc
+ dev_err(wcss->dev, "Failed to get prng clock\n");
+ return ret;
+ }
++
++ wcss->qdss_clk = devm_clk_get(wcss->dev, "qdss");
++ if (IS_ERR(wcss->qdss_clk)) {
++ wcss->qdss_clk = NULL;
++ }
++
+ return 0;
+ }
+
diff --git a/target/linux/qualcommax/patches-6.1/0909-arm64-dts-qcom-ipq6018-assign-QDSS_AT-clock-to-wifi-.patch b/target/linux/qualcommax/patches-6.1/0909-arm64-dts-qcom-ipq6018-assign-QDSS_AT-clock-to-wifi-.patch
new file mode 100644
index 0000000000..0a7a100f6a
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0909-arm64-dts-qcom-ipq6018-assign-QDSS_AT-clock-to-wifi-.patch
@@ -0,0 +1,26 @@
+From 71f30e25d21ae4981ecef6653a4ba7dfeb80db7b Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 23 Jan 2024 11:04:57 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: assign QDSS_AT clock to wifi remoteproc
+
+IPQ6018 needs to enable QDSS_AT clock when loading wifi firmware,
+add it to wifi remoteproc clock list.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 15 ++++++++-------
+ 1 file changed, 9 insertions(+), 8 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -928,8 +928,8 @@
+ "wcss_reset",
+ "wcss_q6_reset";
+
+- clocks = <&gcc GCC_PRNG_AHB_CLK>;
+- clock-names = "prng";
++ clocks = <&gcc GCC_PRNG_AHB_CLK>, <&gcc GCC_QDSS_AT_CLK>;
++ clock-names = "prng", "qdss" ;
+
+ qcom,halt-regs = <&tcsr 0x18000 0x1b000 0xe000>;
+
diff --git a/target/linux/qualcommax/patches-6.1/0910-arm64-dts-qcom-ipq6018-change-voltage-to-perf-levels.patch b/target/linux/qualcommax/patches-6.1/0910-arm64-dts-qcom-ipq6018-change-voltage-to-perf-levels.patch
new file mode 100644
index 0000000000..17784235c3
--- /dev/null
+++ b/target/linux/qualcommax/patches-6.1/0910-arm64-dts-qcom-ipq6018-change-voltage-to-perf-levels.patch
@@ -0,0 +1,65 @@
+From c67a1814bb1d0df290cf1e3f9c966f04aa41b9b9 Mon Sep 17 00:00:00 2001
+From: Mantas Pucka <mantas@8devices.com>
+Date: Tue, 30 Jan 2024 12:43:56 +0200
+Subject: [PATCH] arm64: dts: qcom: ipq6018: change voltage to perf levels for
+ CPR4 driver
+
+Current CPR4 driver requires opp-microvolt to be an abstract
+performance level instead of actual voltage level.
+
+Signed-off-by: Mantas Pucka <mantas@8devices.com>
+---
+ arch/arm64/boot/dts/qcom/ipq6018.dtsi | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+@@ -106,42 +106,42 @@
+
+ opp-864000000 {
+ opp-hz = /bits/ 64 <864000000>;
+- opp-microvolt = <725000>;
++ opp-microvolt = <1>;
+ opp-supported-hw = <0xf>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1056000000 {
+ opp-hz = /bits/ 64 <1056000000>;
+- opp-microvolt = <787500>;
++ opp-microvolt = <2>;
+ opp-supported-hw = <0xf>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1320000000 {
+ opp-hz = /bits/ 64 <1320000000>;
+- opp-microvolt = <862500>;
++ opp-microvolt = <3>;
+ opp-supported-hw = <0x3>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1440000000 {
+ opp-hz = /bits/ 64 <1440000000>;
+- opp-microvolt = <925000>;
++ opp-microvolt = <4>;
+ opp-supported-hw = <0x3>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1608000000 {
+ opp-hz = /bits/ 64 <1608000000>;
+- opp-microvolt = <987500>;
++ opp-microvolt = <5>;
+ opp-supported-hw = <0x1>;
+ clock-latency-ns = <200000>;
+ };
+
+ opp-1800000000 {
+ opp-hz = /bits/ 64 <1800000000>;
+- opp-microvolt = <1062500>;
++ opp-microvolt = <6>;
+ opp-supported-hw = <0x1>;
+ clock-latency-ns = <200000>;
+ };