From f66cf9d38c3d013902543f7ab09bee1bedc8f7a4 Mon Sep 17 00:00:00 2001 From: lean Date: Tue, 13 Sep 2022 10:45:32 +0800 Subject: [PATCH] ipq807x: add kernel 5.15 support --- target/linux/ipq807x/Makefile | 1 + target/linux/ipq807x/config-5.15 | 568 ++ .../arm64/boot/dts/qcom/ipq8071-mf269.dts | 0 .../arch/arm64/boot/dts/qcom/ipq8072-301w.dts | 0 .../arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi | 0 .../arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi | 0 .../arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi | 0 .../boot/dts/qcom/ipq8074-memory-512m.dtsi | 0 .../arm64/boot/dts/qcom/ipq8078-xtr10890.dts | 0 .../drivers/hwmon/emc2305.c | 0 .../arm64/boot/dts/qcom/ipq8071-mf269.dts | 821 +++ .../arch/arm64/boot/dts/qcom/ipq8072-301w.dts | 819 +++ .../arm64/boot/dts/qcom/ipq8074-512m.dtsi | 19 + .../arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi | 176 + .../arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi | 258 + .../boot/dts/qcom/ipq8074-cpr-regulator.dtsi | 226 + .../arch/arm64/boot/dts/qcom/ipq8074-ess.dtsi | 170 + .../arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi | 261 + .../arm64/boot/dts/qcom/ipq8078-xtr10890.dts | 969 ++++ .../files-5.15/drivers/power/qcom/Kconfig | 7 + .../files-5.15/drivers/power/qcom/Makefile | 1 + .../files-5.15/drivers/power/qcom/apm.c | 944 +++ .../drivers/regulator/cpr3-npu-regulator.c | 695 +++ .../drivers/regulator/cpr3-regulator.c | 5112 +++++++++++++++++ .../drivers/regulator/cpr3-regulator.h | 1211 ++++ .../files-5.15/drivers/regulator/cpr3-util.c | 2750 +++++++++ .../drivers/regulator/cpr4-apss-regulator.c | 1819 ++++++ .../files-5.15/include/linux/power/qcom/apm.h | 48 + .../files-5.15/include/soc/qcom/socinfo.h | 463 ++ ...-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch | 43 + ...4-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch | 40 + ...sm8996-Move-clock-cells-to-QMP-PHY-c.patch | 134 + ...qcom-Correct-QMP-PHY-child-node-name.patch | 273 + ...-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch | 94 + ...-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch | 36 + ...qcom-socinfo-Add-IPQ8074-family-ID-s.patch | 50 + ...-Support-reserved-memory-description.patch | 166 + ...-Don-t-print-error-message-on-EPROBE.patch | 32 + ...8-arm64-dts-ipq8074-add-SMEM-support.patch | 49 + ...ts-ipq8074-add-reserved-memory-nodes.patch | 75 + ...dd-IPQ8074-PCIe-Gen3-QMP-PHY-support.patch | 369 ++ ...om-ipq8074-enable-the-GICv2m-support.patch | 34 + ...ve-GEN3_RELATED-DBI-register-to-comm.patch | 45 + ...slot-capabilities-using-PCI_EXP_SLTC.patch | 45 + .../0107-PCI-qcom-Add-IPQ60xx-support.patch | 212 + ...08-PCI-qcom-add-IPQ8074-Gen3-support.patch | 45 + ...09-PCI-qcom-fix-IPQ8074-Gen2-support.patch | 95 + ...dts-ipq8074-fix-PCI-related-DT-nodes.patch | 193 + ...remoteproc-qcom-Add-PRNG-proxy-clock.patch | 155 + ...moteproc-qcom-Add-secure-PIL-support.patch | 143 + ...Add-support-for-split-q6-m3-wlan-fir.patch | 103 + ...oc-qcom-Add-ssr-subdevice-identifier.patch | 24 + ...Update-regmap-offsets-for-halt-regis.patch | 79 + ...ngs-clock-qcom-Add-reset-for-WCSSAON.patch | 25 + .../0117-clk-qcom-Add-WCSSAON-reset.patch | 25 + ...c-wcss-disable-auto-boot-for-IPQ8074.patch | 47 + ...com-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch | 126 + ...0120-arm64-dts-ipq8074-Add-WLAN-node.patch | 134 + ...ermal-qcom-tsens-Add-IPQ8074-support.patch | 99 + ...-arm64-dts-ipq8074-add-thermal-nodes.patch | 128 + ...qcom_spmi-Add-PMD9655-SPMI-regulator.patch | 38 + ...pmi-SMPS-range-is-added-to-support-P.patch | 22 + ...pmi-Initialize-slew-rate-only-if-req.patch | 34 + ...pmi-Add-support-for-VMPWM_CTL-subtyp.patch | 159 + ...dhc-Fixed-SDR104-mode-card-detection.patch | 42 + ...ulator-Add-separate-voltage-range-fo.patch | 48 + ...dts-ipq8074-add-SPMI-PMIC-regulators.patch | 65 + ...k-alpha-pll-add-support-for-APSS-PLL.patch | 47 + ...bindings-for-IPQ8074-APSS-clock-cont.patch | 30 + ...om-Add-IPQ8074-APSS-clock-controller.patch | 220 + ...s-ipc-add-IPQ8074-APSS-clock-control.patch | 39 + ...4-update-APCS-node-due-to-clock-supp.patch | 28 + ...-ipq8074-add-CPU-clock-and-regulator.patch | 66 + ...-arm64-dts-ipq8074-add-label-to-cpus.patch | 25 + ...q8074-add-cooling-cells-to-CPU-nodes.patch | 48 + ...-support-for-resetting-multiple-bits.patch | 56 + ...pq8074-add-missing-networking-resets.patch | 62 + ...-clk-qcom-ipq8074-fix-NSS-core-PLL-s.patch | 80 + ...q8074-disable-USB-GDSC-s-SW_COLLAPSE.patch | 30 + ...074-SW-workaround-for-UBI32-PLL-lock.patch | 31 + ...pq8074-fix-NSS-port-frequency-tables.patch | 64 + ...lk-qcom-ipq8074-add-PPE-crypto-clock.patch | 61 + ...-set-BRANCH_HALT_DELAY-flag-for-UBI-.patch | 100 + ...ort-added-for-necessary-clocks-and-r.patch | 311 + ...-Fix-gcc_snoc_bus_timeout_ahb_clk-of.patch | 44 + ...fer-from-disabling-gcc_sleep_clk_src.patch | 31 + ...074-fix-gcc_blsp1_ahb_clk-properties.patch | 41 + ...ng-RCG-clock-rate-for-high-parent-fr.patch | 49 + ...om-add-support-for-hw-controlled-RCG.patch | 136 + ...-qcom-ipq8074-add-hw-controlled-flag.patch | 38 + ...com-clk-rcg2-fix-set-duty-cycle-call.patch | 11 + ...egulator-add-Qualcomm-CPR-regulators.patch | 66 + .../301-power-Add-Qualcomm-APM.patch | 27 + .../600-5.15-qca-nss-ecm-support-CORE.patch | 615 ++ ...ter-export-udp_get_timeouts-function.patch | 35 + ...2-5.15-qca-add-pppoe-offload-support.patch | 602 ++ ...-identify-ingress-shaping-done-for-e.patch | 75 + ...5-qca-add-add-nss-bridge-mgr-support.patch | 95 + ...-qca-nss-ecm-bonding-add-bond_get_id.patch | 81 + ...dd-bridge-join-and-leave-netdev-cmds.patch | 11 + .../700-ipq8074-overclock-cpu-2.2ghz.patch | 50 + .../900-arm64-dts-add-OpenWrt-DTS-files.patch | 25 + ...rm64-dts-ipq8074-add-label-to-clocks.patch | 24 + ...a-cache-routines-with-same-API-as-32.patch | 29 + ...-__dma_inv_area-and-__dma_clean_area.patch | 56 + 105 files changed, 24303 insertions(+) create mode 100644 target/linux/ipq807x/config-5.15 rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts (100%) rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8072-301w.dts (100%) rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi (100%) rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi (100%) rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi (100%) rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8074-memory-512m.dtsi (100%) rename target/linux/ipq807x/{files => files-5.10}/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts (100%) rename target/linux/ipq807x/{files => files-5.10}/drivers/hwmon/emc2305.c (100%) create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8072-301w.dts create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-512m.dtsi create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-cpr-regulator.dtsi create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ess.dtsi create mode 100644 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi create mode 100755 target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts create mode 100644 target/linux/ipq807x/files-5.15/drivers/power/qcom/Kconfig create mode 100644 target/linux/ipq807x/files-5.15/drivers/power/qcom/Makefile create mode 100644 target/linux/ipq807x/files-5.15/drivers/power/qcom/apm.c create mode 100644 target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-npu-regulator.c create mode 100644 target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.c create mode 100644 target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.h create mode 100644 target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-util.c create mode 100644 target/linux/ipq807x/files-5.15/drivers/regulator/cpr4-apss-regulator.c create mode 100644 target/linux/ipq807x/files-5.15/include/linux/power/qcom/apm.h create mode 100644 target/linux/ipq807x/files-5.15/include/soc/qcom/socinfo.h create mode 100644 target/linux/ipq807x/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch create mode 100644 target/linux/ipq807x/patches-5.15/0002-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch create mode 100644 target/linux/ipq807x/patches-5.15/0003-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch create mode 100644 target/linux/ipq807x/patches-5.15/0004-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch create mode 100644 target/linux/ipq807x/patches-5.15/0005-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch create mode 100644 target/linux/ipq807x/patches-5.15/0006-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch create mode 100644 target/linux/ipq807x/patches-5.15/0007-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch create mode 100644 target/linux/ipq807x/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch create mode 100644 target/linux/ipq807x/patches-5.15/0010-v5.18-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch create mode 100644 target/linux/ipq807x/patches-5.15/0011-v5.18-arm64-dts-ipq8074-add-SMEM-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0101-arm64-dts-ipq8074-add-reserved-memory-nodes.patch create mode 100644 target/linux/ipq807x/patches-5.15/0103-phy-qcom-qmp-Add-IPQ8074-PCIe-Gen3-QMP-PHY-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0104-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0105-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch create mode 100644 target/linux/ipq807x/patches-5.15/0106-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch create mode 100644 target/linux/ipq807x/patches-5.15/0107-PCI-qcom-Add-IPQ60xx-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0108-PCI-qcom-add-IPQ8074-Gen3-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0109-PCI-qcom-fix-IPQ8074-Gen2-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0110-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch create mode 100644 target/linux/ipq807x/patches-5.15/0111-remoteproc-qcom-Add-PRNG-proxy-clock.patch create mode 100644 target/linux/ipq807x/patches-5.15/0112-remoteproc-qcom-Add-secure-PIL-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0113-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch create mode 100644 target/linux/ipq807x/patches-5.15/0114-remoteproc-qcom-Add-ssr-subdevice-identifier.patch create mode 100644 target/linux/ipq807x/patches-5.15/0115-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch create mode 100644 target/linux/ipq807x/patches-5.15/0116-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch create mode 100644 target/linux/ipq807x/patches-5.15/0117-clk-qcom-Add-WCSSAON-reset.patch create mode 100644 target/linux/ipq807x/patches-5.15/0118-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch create mode 100644 target/linux/ipq807x/patches-5.15/0119-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch create mode 100644 target/linux/ipq807x/patches-5.15/0120-arm64-dts-ipq8074-Add-WLAN-node.patch create mode 100644 target/linux/ipq807x/patches-5.15/0121-thermal-qcom-tsens-Add-IPQ8074-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/0122-arm64-dts-ipq8074-add-thermal-nodes.patch create mode 100644 target/linux/ipq807x/patches-5.15/0123-regulator-qcom_spmi-Add-PMD9655-SPMI-regulator.patch create mode 100644 target/linux/ipq807x/patches-5.15/0124-regulator-qcom_spmi-SMPS-range-is-added-to-support-P.patch create mode 100644 target/linux/ipq807x/patches-5.15/0125-regulator-qcom_spmi-Initialize-slew-rate-only-if-req.patch create mode 100644 target/linux/ipq807x/patches-5.15/0126-regulator-qcom_spmi-Add-support-for-VMPWM_CTL-subtyp.patch create mode 100644 target/linux/ipq807x/patches-5.15/0127-ipq807x-sdhc-Fixed-SDR104-mode-card-detection.patch create mode 100644 target/linux/ipq807x/patches-5.15/0128-ipq807x-spmi-regulator-Add-separate-voltage-range-fo.patch create mode 100644 target/linux/ipq807x/patches-5.15/0129-arm64-dts-ipq8074-add-SPMI-PMIC-regulators.patch create mode 100644 target/linux/ipq807x/patches-5.15/0130-clk-qcom-clk-alpha-pll-add-support-for-APSS-PLL.patch create mode 100644 target/linux/ipq807x/patches-5.15/0131-clk-qcom-Add-DT-bindings-for-IPQ8074-APSS-clock-cont.patch create mode 100644 target/linux/ipq807x/patches-5.15/0132-clk-qcom-Add-IPQ8074-APSS-clock-controller.patch create mode 100644 target/linux/ipq807x/patches-5.15/0133-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-control.patch create mode 100644 target/linux/ipq807x/patches-5.15/0134-arm64-dts-ipq8074-update-APCS-node-due-to-clock-supp.patch create mode 100644 target/linux/ipq807x/patches-5.15/0135-arm64-dts-ipq8074-add-CPU-clock-and-regulator.patch create mode 100644 target/linux/ipq807x/patches-5.15/0136-arm64-dts-ipq8074-add-label-to-cpus.patch create mode 100644 target/linux/ipq807x/patches-5.15/0137-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch create mode 100644 target/linux/ipq807x/patches-5.15/0138-clk-ipq-support-for-resetting-multiple-bits.patch create mode 100644 target/linux/ipq807x/patches-5.15/0139-clk-qcom-ipq8074-add-missing-networking-resets.patch create mode 100644 target/linux/ipq807x/patches-5.15/0140-clk-qcom-ipq8074-fix-NSS-core-PLL-s.patch create mode 100644 target/linux/ipq807x/patches-5.15/0141-clk-qcom-ipq8074-disable-USB-GDSC-s-SW_COLLAPSE.patch create mode 100644 target/linux/ipq807x/patches-5.15/0142-clk-qcom-ipq8074-SW-workaround-for-UBI32-PLL-lock.patch create mode 100644 target/linux/ipq807x/patches-5.15/0143-clk-qcom-ipq8074-fix-NSS-port-frequency-tables.patch create mode 100644 target/linux/ipq807x/patches-5.15/0145-clk-qcom-ipq8074-add-PPE-crypto-clock.patch create mode 100644 target/linux/ipq807x/patches-5.15/0146-clk-qcom-ipq8074-set-BRANCH_HALT_DELAY-flag-for-UBI-.patch create mode 100644 target/linux/ipq807x/patches-5.15/0151-clk-ipq8074-Support-added-for-necessary-clocks-and-r.patch create mode 100644 target/linux/ipq807x/patches-5.15/0152-clk-qcom-ipq8074-Fix-gcc_snoc_bus_timeout_ahb_clk-of.patch create mode 100644 target/linux/ipq807x/patches-5.15/0154-clk-ipq8074-defer-from-disabling-gcc_sleep_clk_src.patch create mode 100644 target/linux/ipq807x/patches-5.15/0155-clk-ipq8074-fix-gcc_blsp1_ahb_clk-properties.patch create mode 100644 target/linux/ipq807x/patches-5.15/0156-clk-qcom-fix-wrong-RCG-clock-rate-for-high-parent-fr.patch create mode 100644 target/linux/ipq807x/patches-5.15/0157-clk-qcom-add-support-for-hw-controlled-RCG.patch create mode 100644 target/linux/ipq807x/patches-5.15/0158-clk-qcom-ipq8074-add-hw-controlled-flag.patch create mode 100644 target/linux/ipq807x/patches-5.15/0159-clk-qcom-clk-rcg2-fix-set-duty-cycle-call.patch create mode 100644 target/linux/ipq807x/patches-5.15/300-regulator-add-Qualcomm-CPR-regulators.patch create mode 100644 target/linux/ipq807x/patches-5.15/301-power-Add-Qualcomm-APM.patch create mode 100644 target/linux/ipq807x/patches-5.15/600-5.15-qca-nss-ecm-support-CORE.patch create mode 100644 target/linux/ipq807x/patches-5.15/601-5.15-netfilter-export-udp_get_timeouts-function.patch create mode 100644 target/linux/ipq807x/patches-5.15/602-5.15-qca-add-pppoe-offload-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/603-net-core-Flag-to-identify-ingress-shaping-done-for-e.patch create mode 100644 target/linux/ipq807x/patches-5.15/605-5.15-qca-add-add-nss-bridge-mgr-support.patch create mode 100644 target/linux/ipq807x/patches-5.15/606-5.15-qca-nss-ecm-bonding-add-bond_get_id.patch create mode 100644 target/linux/ipq807x/patches-5.15/607-5.15-qca-nss-ecm-Add-bridge-join-and-leave-netdev-cmds.patch create mode 100644 target/linux/ipq807x/patches-5.15/700-ipq8074-overclock-cpu-2.2ghz.patch create mode 100644 target/linux/ipq807x/patches-5.15/900-arm64-dts-add-OpenWrt-DTS-files.patch create mode 100644 target/linux/ipq807x/patches-5.15/901-arm64-dts-ipq8074-add-label-to-clocks.patch create mode 100644 target/linux/ipq807x/patches-5.15/902-5.15-arm64-provide-dma-cache-routines-with-same-API-as-32.patch create mode 100644 target/linux/ipq807x/patches-5.15/903-5.15-arm64-mm-export-__dma_inv_area-and-__dma_clean_area.patch diff --git a/target/linux/ipq807x/Makefile b/target/linux/ipq807x/Makefile index 3947a8ab7..c8af6c42c 100644 --- a/target/linux/ipq807x/Makefile +++ b/target/linux/ipq807x/Makefile @@ -9,6 +9,7 @@ CPU_TYPE:=cortex-a53 SUBTARGETS:=generic KERNEL_PATCHVER:=5.10 +KERNEL_TESTING_PATCHVER:=5.15 include $(INCLUDE_DIR)/target.mk DEFAULT_PACKAGES += \ diff --git a/target/linux/ipq807x/config-5.15 b/target/linux/ipq807x/config-5.15 new file mode 100644 index 000000000..232a70f47 --- /dev/null +++ b/target/linux/ipq807x/config-5.15 @@ -0,0 +1,568 @@ +CONFIG_64BIT=y +CONFIG_AF_UNIX_OOB=y +# CONFIG_APQ_GCC_8084 is not set +# CONFIG_APQ_MMCC_8084 is not set +CONFIG_AQUANTIA_PHY=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_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_ARM64=y +CONFIG_ARM64_4K_PAGES=y +CONFIG_ARM64_CRYPTO=y +CONFIG_ARM64_ERRATUM_1165522=y +CONFIG_ARM64_ERRATUM_1286807=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_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_ARM_AMBA=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +CONFIG_ARM_CPUIDLE=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 is not set +CONFIG_ASN1=y +CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y +CONFIG_BINARY_PRINTF=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_STACKPROTECTOR_SYSREG=y +CONFIG_CLANG_VERSION=0 +CONFIG_CLONE_BACKWARDS=y +CONFIG_CLZ_TAB=y +CONFIG_COMMON_CLK=y +CONFIG_COMMON_CLK_QCOM=y +# CONFIG_COMPAT_32BIT_TIME is not set +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 is not set +CONFIG_CPU_FREQ_THERMAL=y +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_CPU_IDLE_MULTIPLE_DRIVERS=y +CONFIG_CPU_PM=y +CONFIG_CPU_RMAP=y +CONFIG_CPU_THERMAL=y +CONFIG_CRC16=y +CONFIG_CRC8=y +CONFIG_CRYPTO_AES_ARM64=y +CONFIG_CRYPTO_AES_ARM64_BS=y +CONFIG_CRYPTO_AES_ARM64_CE=y +CONFIG_CRYPTO_AES_ARM64_CE_BLK=y +CONFIG_CRYPTO_AES_ARM64_CE_CCM=y +CONFIG_CRYPTO_AES_ARM64_NEON_BLK=y +CONFIG_CRYPTO_ANSI_CPRNG=y +CONFIG_CRYPTO_ARCH_HAVE_LIB_CHACHA=y +CONFIG_CRYPTO_ARCH_HAVE_LIB_POLY1305=y +CONFIG_CRYPTO_AUTHENC=y +CONFIG_CRYPTO_BLAKE2B=y +CONFIG_CRYPTO_BLAKE2S=y +CONFIG_CRYPTO_CBC=y +CONFIG_CRYPTO_CFB=y +CONFIG_CRYPTO_CHACHA20=y +CONFIG_CRYPTO_CHACHA20POLY1305=y +CONFIG_CRYPTO_CHACHA20_NEON=y +CONFIG_CRYPTO_CMAC=m +CONFIG_CRYPTO_CRCT10DIF=y +CONFIG_CRYPTO_CRYPTD=y +CONFIG_CRYPTO_CURVE25519=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_DH=y +CONFIG_CRYPTO_DRBG=y +CONFIG_CRYPTO_DRBG_HMAC=y +CONFIG_CRYPTO_DRBG_MENU=y +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_ECC=y +CONFIG_CRYPTO_ECDH=y +CONFIG_CRYPTO_ECDSA=y +CONFIG_CRYPTO_ECRDSA=y +CONFIG_CRYPTO_GHASH_ARM64_CE=y +CONFIG_CRYPTO_HASH_INFO=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_HW=y +CONFIG_CRYPTO_JITTERENTROPY=y +CONFIG_CRYPTO_KEYWRAP=y +CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y +CONFIG_CRYPTO_LIB_CHACHA_GENERIC=y +CONFIG_CRYPTO_LIB_CURVE25519_GENERIC=y +CONFIG_CRYPTO_LIB_DES=y +CONFIG_CRYPTO_LIB_POLY1305_GENERIC=y +CONFIG_CRYPTO_LIB_SHA256=y +CONFIG_CRYPTO_LIB_SM4=y +CONFIG_CRYPTO_LRW=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_NHPOLY1305=y +CONFIG_CRYPTO_NHPOLY1305_NEON=y +CONFIG_CRYPTO_OFB=y +CONFIG_CRYPTO_POLY1305=y +CONFIG_CRYPTO_POLY1305_NEON=y +CONFIG_CRYPTO_RNG=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_RNG_DEFAULT=y +CONFIG_CRYPTO_RSA=y +CONFIG_CRYPTO_SHA1=y +CONFIG_CRYPTO_SHA1_ARM64_CE=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA256_ARM64=y +CONFIG_CRYPTO_SHA2_ARM64_CE=y +CONFIG_CRYPTO_SHA3=y +CONFIG_CRYPTO_SHA3_ARM64=y +CONFIG_CRYPTO_SHA512=y +CONFIG_CRYPTO_SHA512_ARM64=y +CONFIG_CRYPTO_SHA512_ARM64_CE=y +CONFIG_CRYPTO_SIMD=y +CONFIG_CRYPTO_SM2=y +CONFIG_CRYPTO_SM3=y +CONFIG_CRYPTO_SM3_ARM64_CE=y +CONFIG_CRYPTO_SM4_ARM64_CE=y +CONFIG_CRYPTO_STREEBOG=y +CONFIG_CRYPTO_XTS=y +CONFIG_CRYPTO_XXHASH=y +CONFIG_CRYPTO_ZSTD=y +CONFIG_DCACHE_WORD_ACCESS=y +CONFIG_DEV_COREDUMP=y +CONFIG_DMADEVICES=y +CONFIG_DMA_DIRECT_REMAP=y +CONFIG_DMA_ENGINE=y +CONFIG_DMA_OF=y +CONFIG_DMA_REMAP=y +CONFIG_DMA_VIRTUAL_CHANNELS=y +CONFIG_DTC=y +CONFIG_DT_IDLE_STATES=y +CONFIG_EDAC_SUPPORT=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_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_FIND_FIRST_BIT=y +CONFIG_GENERIC_GETTIMEOFDAY=y +CONFIG_GENERIC_IDLE_POLL_SETUP=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=y +CONFIG_GPIOLIB_IRQCHIP=y +CONFIG_GPIO_CDEV=y +CONFIG_HANDLE_DOMAIN_IRQ=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_ILLEGAL_POINTER_VALUE=0xdead000000000000 +CONFIG_INITRAMFS_SOURCE="" +# CONFIG_IPQ_APSS_6018 is not set +CONFIG_IPQ_APSS_8074=y +# CONFIG_IPQ_APSS_PLL is not set +# 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=y +# 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_LLD_VERSION=0 +CONFIG_LOCK_DEBUGGING_SUPPORT=y +CONFIG_LOCK_SPIN_ON_OWNER=y +CONFIG_LTO_NONE=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_HI6421_SPMI is not set +# CONFIG_MFD_QCOM_RPM is not set +CONFIG_MFD_SPMI_PMIC=y +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_MPILIB=y +# CONFIG_MSM_GCC_8660 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_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_SPMI_SDAM is not set +CONFIG_NVMEM_SYSFS=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_OID_REGISTRY=y +CONFIG_PADATA=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_PHYS_ADDR_T_64BIT=y +# CONFIG_PHY_QCOM_APQ8064_SATA 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_APQ8064 is not set +# CONFIG_PINCTRL_APQ8084 is not set +# CONFIG_PINCTRL_IPQ4019 is not set +# CONFIG_PINCTRL_IPQ6018 is not set +# CONFIG_PINCTRL_IPQ8064 is not set +CONFIG_PINCTRL_IPQ8074=y +# CONFIG_PINCTRL_MDM9615 is not set +CONFIG_PINCTRL_MSM=y +# CONFIG_PINCTRL_MSM8226 is not set +# CONFIG_PINCTRL_MSM8660 is not set +# CONFIG_PINCTRL_MSM8916 is not set +# CONFIG_PINCTRL_MSM8960 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_QCOM_SPMI_PMIC 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_SDM660 is not set +# CONFIG_PINCTRL_SDM845 is not set +# CONFIG_PINCTRL_SM8150 is not set +# CONFIG_PINCTRL_SM8250 is not set +CONFIG_PM=y +# CONFIG_PM8916_WATCHDOG is not set +CONFIG_PM_CLK=y +CONFIG_PM_OPP=y +CONFIG_POWER_RESET=y +# CONFIG_POWER_RESET_MSM is not set +# CONFIG_POWER_RESET_QCOM_PON is not set +CONFIG_POWER_SUPPLY=y +CONFIG_PRINTK_TIME=y +CONFIG_PTP_1588_CLOCK_OPTIONAL=y +# CONFIG_QCOM_A53PLL is not set +# CONFIG_QCOM_AOSS_QMP is not set +CONFIG_QCOM_APCS_IPC=y +CONFIG_QCOM_APM=y +# 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_COINCELL 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_IPCC is not set +# CONFIG_QCOM_LLCC is not set +CONFIG_QCOM_MDT_LOADER=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_QFPROM=y +# CONFIG_QCOM_QMI_HELPERS is not set +# 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_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_RAS=y +CONFIG_RATIONAL=y +CONFIG_REGMAP=y +CONFIG_REGMAP_MMIO=y +CONFIG_REGMAP_SPMI=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_CPR3=y +# CONFIG_REGULATOR_CPR3_NPU is not set +CONFIG_REGULATOR_CPR4_APSS=y +# CONFIG_REGULATOR_QCOM_LABIBB is not set +CONFIG_REGULATOR_QCOM_SPMI=y +# CONFIG_REGULATOR_QCOM_USB_VBUS is not set +# 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_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_RPS=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_DISPCC_7180 is not set +# CONFIG_SC_GCC_7180 is not set +# CONFIG_SC_GPUCC_7180 is not set +# CONFIG_SC_LPASS_CORECC_7180 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_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_SHORTCUT_FE is not set +CONFIG_SMP=y +# CONFIG_SM_GCC_8150 is not set +# CONFIG_SM_GCC_8250 is not set +# CONFIG_SM_GPUCC_8150 is not set +# CONFIG_SM_GPUCC_8250 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_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_SPMI=y +# CONFIG_SPMI_HISI3670 is not set +CONFIG_SPMI_MSM_PMIC_ARB=y +# CONFIG_SPMI_PMIC_CLKDIV is not set +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_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_BLK is not set +# CONFIG_VIRTIO_NET is not set +CONFIG_VMAP_STACK=y +CONFIG_WANT_DEV_COREDUMP=y +CONFIG_WATCHDOG_CORE=y +CONFIG_XOR_BLOCKS=y +CONFIG_XPS=y +CONFIG_XXHASH=y +CONFIG_ZLIB_DEFLATE=y +CONFIG_ZLIB_INFLATE=y +CONFIG_ZONE_DMA32=y +CONFIG_ZSTD_COMPRESS=y +CONFIG_ZSTD_DECOMPRESS=y diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8072-301w.dts b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8072-301w.dts similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8072-301w.dts rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8072-301w.dts diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-memory-512m.dtsi b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-memory-512m.dtsi similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8074-memory-512m.dtsi rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8074-memory-512m.dtsi diff --git a/target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts b/target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts similarity index 100% rename from target/linux/ipq807x/files/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts rename to target/linux/ipq807x/files-5.10/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts diff --git a/target/linux/ipq807x/files/drivers/hwmon/emc2305.c b/target/linux/ipq807x/files-5.10/drivers/hwmon/emc2305.c similarity index 100% rename from target/linux/ipq807x/files/drivers/hwmon/emc2305.c rename to target/linux/ipq807x/files-5.10/drivers/hwmon/emc2305.c diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts new file mode 100644 index 000000000..1272a6ae3 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8071-mf269.dts @@ -0,0 +1,821 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT +/* Copyright (c) 2021, Dirk Buchwalder */ + +/dts-v1/; + +#include "ipq8074-512m.dtsi" +#include "ipq8074-ac-cpu.dtsi" +#include "ipq8074-ess.dtsi" +#include "ipq8074-ac-nss.dtsi" + +#include +#include + +/ { + #address-cells = <2>; + #size-cells = <2>; + + model = "QNAP 301w"; + compatible = "qnap,301w", "qcom,ipq8074"; + interrupt-parent = <&intc>; + + aliases { + serial0 = &blsp1_uart5; + /* + * Aliases as required by u-boot + * to patch MAC addresses + */ + led-boot = &led_system_red; + led-failsafe = &led_system_red; + led-running = &led_pwr_green; + led-upgrade = &led_system_red; + ethernet0 = &dp1; + ethernet1 = &dp2; + ethernet2 = &dp3; + ethernet3 = &dp4; + ethernet4 = &dp5; + ethernet5 = &dp6; + label-mac-device = &dp1; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + keys { + compatible = "gpio-keys"; + + wps { + label = "wps"; + gpios = <&tlmm 57 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + + reset { + label = "reset"; + gpios = <&tlmm 67 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + leds { + compatible = "gpio-leds"; + + led_system_green: system-green { + label = "green:system"; + gpios = <&tlmm 1 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_system_red: system-red { + label = "red:system"; + gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_pwr_green: pwr-green { + label = "green:pwr"; + gpios = <&tlmm 4 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_wifi_green: wifi-green { + label = "green:wifi"; + gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan1_green: lan1-green { + label = "green:lan1"; + gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan1_amber: lan1-amber { + label = "amber:lan1"; + gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan2_green: lan2-green { + label = "green:lan2"; + gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan2_amber: lan2-amber { + label = "amber:lan2"; + gpios = <&tlmm 11 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan3_green: lan3-green { + label = "green:lan3"; + gpios = <&tlmm 12 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan3_amber: lan3-amber { + label = "amber:lan3"; + gpios = <&tlmm 13 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan4_green: lan4-green { + label = "green:lan4"; + gpios = <&tlmm 14 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan4_amber: lan4-amber { + label = "amber:lan4"; + gpios = <&tlmm 15 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_1_green: 10g_1-green { + label = "green:10g_1"; + gpios = <&tlmm 54 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_1_amber: 10g_1-amber { + label = "amber:10g_1"; + gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_2_green: 10g_2-green { + label = "green:10g_2"; + gpios = <&tlmm 51 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_2_amber: 10g_2-amber { + label = "amber:10g_2"; + gpios = <&tlmm 52 GPIO_ACTIVE_HIGH>; + color = ; + }; + }; +}; + +&tlmm { + + mdio_pins: mdio-pins { + mdc { + pins = "gpio68"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + + mdio { + pins = "gpio69"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + button_pins: button_pins { + wps_button { + pins = "gpio57"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + + rst_button { + pins = "gpio67"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + qpic_pins: qpic-pins { + status = "disabled"; + /*disabling qpic-pins due to the leds are using all of the gpios*/ + }; + + leds_pins: leds_pinmux { + led_pwr_green { + pins = "gpio4"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_system_green { + pins = "gpio1"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_system_red { + pins = "gpio3"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan1_green { + pins = "gpio6"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan1_amber { + pins = "gpio7"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan2_green { + pins = "gpio8"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan2_amber { + pins = "gpio11"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan3_green { + pins = "gpio12"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan3_amber { + pins = "gpio13"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan4_green { + pins = "gpio14"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan4_amber { + pins = "gpio15"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_1_green { + pins = "gpio54"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_1_amber { + pins = "gpio56"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_2_green { + pins = "gpio51"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_2_amber { + pins = "gpio52"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_wifi_green { + pins = "gpio42"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; +}; + +&blsp1_uart5 { + status = "okay"; +}; + +&prng { + status = "okay"; +}; + +&ssphy_0 { + status = "okay"; +}; + +&qusb_phy_0 { + status = "okay"; +}; + +&ssphy_1 { + status = "okay"; +}; + +&qusb_phy_1 { + status = "okay"; +}; + +&usb_0 { + status = "okay"; +}; + +&usb_1 { + status = "okay"; +}; + +&cryptobam { + status = "okay"; +}; + +&crypto { + status = "okay"; +}; + +&qpic_bam { + status = "okay"; +}; + +&blsp1_spi1 { /* BLSP1 QUP1 */ + pinctrl-0 = <&spi_0_pins>; + pinctrl-names = "default"; + cs-gpios = <0>; + status = "okay"; + + flash@0 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; + + partitions { + compatible = "qcom,smem-part"; + }; + }; +}; + +&mdio { + status = "okay"; + + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + reset-gpios = <&tlmm 37 GPIO_ACTIVE_LOW>; + + ethernet-phy@0 { + compatible ="ethernet-phy-ieee802.3-c45"; + reg = <0>; + reset-gpios = <&tlmm 59 GPIO_ACTIVE_LOW>; + }; + + ethernet-phy@8 { + compatible ="ethernet-phy-ieee802.3-c45"; + reg = <8>; + reset-gpios = <&tlmm 44 GPIO_ACTIVE_LOW>; + }; + + ethernet-phy@16 { + reg = <16>; + }; + + ethernet-phy@17 { + reg = <17>; + }; + + ethernet-phy@18 { + reg = <18>; + }; + + ethernet-phy@19 { + reg = <19>; + }; + +}; + +&sdhc_1 { + /* According to the stock dts from the QNAP gpl drop + * the emmc has a problem with the hs400 > hs200 speed switch. + * Therefore remove the mmc-hs400-1_8v property + */ + /delete-property/ mmc-hs400-1_8v; + mmc-hs200-1_8v; + mmc-ddr-1_8v; + vqmmc-supply = <&ldo11>; + status = "okay"; +}; + +&switch { + status = "okay"; + + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x3e>; /* lan port bitmap */ + switch_wan_bmp = <0xc0>; /* wan port bitmap */ + switch_mac_mode = <0xb>; /* mac mode for uniphy instance0*/ + switch_mac_mode1 = <0xd>; /* mac mode for uniphy instance1*/ + switch_mac_mode2 = <0xd>; /* mac mode for uniphy instance2*/ + bm_tick_mode = <0>; /* bm tick mode */ + tm_tick_mode = <0>; /* tm tick mode */ + + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <16>; + }; + port@1 { + port_id = <2>; + phy_address = <17>; + }; + port@2 { + port_id = <3>; + phy_address = <18>; + }; + port@3 { + port_id = <4>; + phy_address = <19>; + }; + port@4 { + port_id = <5>; + phy_address = <8>; + compatible = "ethernet-phy-ieee802.3-c45"; + ethernet-phy-ieee802.3-c45; + }; + port@5 { + port_id = <6>; + phy_address = <0>; + compatible = "ethernet-phy-ieee802.3-c45"; + ethernet-phy-ieee802.3-c45; + }; + }; + + port_scheduler_resource { + port@0 { + port_id = <0>; + ucast_queue = <0 143>; + mcast_queue = <256 271>; + l0sp = <0 35>; + l0cdrr = <0 47>; + l0edrr = <0 47>; + l1cdrr = <0 7>; + l1edrr = <0 7>; + }; + port@1 { + port_id = <1>; + ucast_queue = <144 159>; + mcast_queue = <272 275>; + l0sp = <36 39>; + l0cdrr = <48 63>; + l0edrr = <48 63>; + l1cdrr = <8 11>; + l1edrr = <8 11>; + }; + port@2 { + port_id = <2>; + ucast_queue = <160 175>; + mcast_queue = <276 279>; + l0sp = <40 43>; + l0cdrr = <64 79>; + l0edrr = <64 79>; + l1cdrr = <12 15>; + l1edrr = <12 15>; + }; + port@3 { + port_id = <3>; + ucast_queue = <176 191>; + mcast_queue = <280 283>; + l0sp = <44 47>; + l0cdrr = <80 95>; + l0edrr = <80 95>; + l1cdrr = <16 19>; + l1edrr = <16 19>; + }; + port@4 { + port_id = <4>; + ucast_queue = <192 207>; + mcast_queue = <284 287>; + l0sp = <48 51>; + l0cdrr = <96 111>; + l0edrr = <96 111>; + l1cdrr = <20 23>; + l1edrr = <20 23>; + }; + port@5 { + port_id = <5>; + ucast_queue = <208 223>; + mcast_queue = <288 291>; + l0sp = <52 55>; + l0cdrr = <112 127>; + l0edrr = <112 127>; + l1cdrr = <24 27>; + l1edrr = <24 27>; + }; + port@6 { + port_id = <6>; + ucast_queue = <224 239>; + mcast_queue = <292 295>; + l0sp = <56 59>; + l0cdrr = <128 143>; + l0edrr = <128 143>; + l1cdrr = <28 31>; + l1edrr = <28 31>; + }; + port@7 { + port_id = <7>; + ucast_queue = <240 255>; + mcast_queue = <296 299>; + l0sp = <60 63>; + l0cdrr = <144 159>; + l0edrr = <144 159>; + l1cdrr = <32 35>; + l1edrr = <32 35>; + }; + }; + port_scheduler_config { + port@0 { + port_id = <0>; + l1scheduler { + group@0 { + sp = <0 1>; /*L0 SPs*/ + /*cpri cdrr epri edrr*/ + cfg = <0 0 0 0>; + }; + }; + l0scheduler { + group@0 { + /*unicast queues*/ + ucast_queue = <0 4 8>; + /*multicast queues*/ + mcast_queue = <256 260>; + /*sp cpri cdrr epri edrr*/ + cfg = <0 0 0 0 0>; + }; + group@1 { + ucast_queue = <1 5 9>; + mcast_queue = <257 261>; + cfg = <0 1 1 1 1>; + }; + group@2 { + ucast_queue = <2 6 10>; + mcast_queue = <258 262>; + cfg = <0 2 2 2 2>; + }; + group@3 { + ucast_queue = <3 7 11>; + mcast_queue = <259 263>; + cfg = <0 3 3 3 3>; + }; + }; + }; + port@1 { + port_id = <1>; + l1scheduler { + group@0 { + sp = <36>; + cfg = <0 8 0 8>; + }; + group@1 { + sp = <37>; + cfg = <1 9 1 9>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <144>; + ucast_loop_pri = <16>; + mcast_queue = <272>; + mcast_loop_pri = <4>; + cfg = <36 0 48 0 48>; + }; + }; + }; + port@2 { + port_id = <2>; + l1scheduler { + group@0 { + sp = <40>; + cfg = <0 12 0 12>; + }; + group@1 { + sp = <41>; + cfg = <1 13 1 13>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <160>; + ucast_loop_pri = <16>; + mcast_queue = <276>; + mcast_loop_pri = <4>; + cfg = <40 0 64 0 64>; + }; + }; + }; + port@3 { + port_id = <3>; + l1scheduler { + group@0 { + sp = <44>; + cfg = <0 16 0 16>; + }; + group@1 { + sp = <45>; + cfg = <1 17 1 17>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <176>; + ucast_loop_pri = <16>; + mcast_queue = <280>; + mcast_loop_pri = <4>; + cfg = <44 0 80 0 80>; + }; + }; + }; + port@4 { + port_id = <4>; + l1scheduler { + group@0 { + sp = <48>; + cfg = <0 20 0 20>; + }; + group@1 { + sp = <49>; + cfg = <1 21 1 21>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <192>; + ucast_loop_pri = <16>; + mcast_queue = <284>; + mcast_loop_pri = <4>; + cfg = <48 0 96 0 96>; + }; + }; + }; + port@5 { + port_id = <5>; + l1scheduler { + group@0 { + sp = <52>; + cfg = <0 24 0 24>; + }; + group@1 { + sp = <53>; + cfg = <1 25 1 25>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <208>; + ucast_loop_pri = <16>; + mcast_queue = <288>; + mcast_loop_pri = <4>; + cfg = <52 0 112 0 112>; + }; + }; + }; + port@6 { + port_id = <6>; + l1scheduler { + group@0 { + sp = <56>; + cfg = <0 28 0 28>; + }; + group@1 { + sp = <57>; + cfg = <1 29 1 29>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <224>; + ucast_loop_pri = <16>; + mcast_queue = <292>; + mcast_loop_pri = <4>; + cfg = <56 0 128 0 128>; + }; + }; + }; + port@7 { + port_id = <7>; + l1scheduler { + group@0 { + sp = <60>; + cfg = <0 32 0 32>; + }; + group@1 { + sp = <61>; + cfg = <1 33 1 33>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <240>; + ucast_loop_pri = <16>; + mcast_queue = <296>; + cfg = <60 0 144 0 144>; + }; + }; + }; + }; +}; + +&edma { + status = "okay"; +}; + +&soc { + dp1: dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <16>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp2: dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <17>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp3: dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <18>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp4: dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <19>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp5: dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a001800 0x200>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <8>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp6: dp6 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <6>; + reg = <0x3a007000 0x3fff>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <0>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; +}; + +&wifi { + status = "okay"; + /* using board_id 0xff is intentionally + * as the stock firmware is also using this default board_id + */ + qcom,board_id = <0xff>; + qcom,ath11k-calibration-variant = "QNAP-301w"; +}; \ No newline at end of file diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8072-301w.dts b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8072-301w.dts new file mode 100644 index 000000000..a8a9161bd --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8072-301w.dts @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT +/* Copyright (c) 2021, Dirk Buchwalder */ + +/dts-v1/; + +#include "ipq8074.dtsi" +#include "ipq8074-hk-cpu.dtsi" +#include "ipq8074-ac-nss.dtsi" +#include +#include +#include + +/ { + #address-cells = <2>; + #size-cells = <2>; + + model = "QNAP 301w"; + compatible = "qnap,301w", "qcom,ipq8074"; + interrupt-parent = <&intc>; + + aliases { + serial0 = &blsp1_uart5; + /* + * Aliases as required by u-boot + * to patch MAC addresses + */ + led-boot = &led_system_red; + led-failsafe = &led_system_red; + led-running = &led_pwr_green; + led-upgrade = &led_system_red; + ethernet0 = &dp1; + ethernet1 = &dp2; + ethernet2 = &dp3; + ethernet3 = &dp4; + ethernet4 = &dp5; + ethernet5 = &dp6; + label-mac-device = &dp1; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + keys { + compatible = "gpio-keys"; + + wps { + label = "wps"; + gpios = <&tlmm 57 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + + reset { + label = "reset"; + gpios = <&tlmm 67 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + leds { + compatible = "gpio-leds"; + + led_system_green: system-green { + label = "green:system"; + gpios = <&tlmm 1 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_system_red: system-red { + label = "red:system"; + gpios = <&tlmm 3 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_pwr_green: pwr-green { + label = "green:pwr"; + gpios = <&tlmm 4 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_wifi_green: wifi-green { + label = "green:wifi"; + gpios = <&tlmm 42 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan1_green: lan1-green { + label = "green:lan1"; + gpios = <&tlmm 6 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan1_amber: lan1-amber { + label = "amber:lan1"; + gpios = <&tlmm 7 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan2_green: lan2-green { + label = "green:lan2"; + gpios = <&tlmm 8 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan2_amber: lan2-amber { + label = "amber:lan2"; + gpios = <&tlmm 11 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan3_green: lan3-green { + label = "green:lan3"; + gpios = <&tlmm 12 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan3_amber: lan3-amber { + label = "amber:lan3"; + gpios = <&tlmm 13 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan4_green: lan4-green { + label = "green:lan4"; + gpios = <&tlmm 14 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_lan4_amber: lan4-amber { + label = "amber:lan4"; + gpios = <&tlmm 15 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_1_green: 10g_1-green { + label = "green:10g_1"; + gpios = <&tlmm 54 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_1_amber: 10g_1-amber { + label = "amber:10g_1"; + gpios = <&tlmm 56 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_2_green: 10g_2-green { + label = "green:10g_2"; + gpios = <&tlmm 51 GPIO_ACTIVE_HIGH>; + color = ; + }; + + led_10g_2_amber: 10g_2-amber { + label = "amber:10g_2"; + gpios = <&tlmm 52 GPIO_ACTIVE_HIGH>; + color = ; + }; + }; +}; + +&tlmm { + + mdio_pins: mdio-pins { + mdc { + pins = "gpio68"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + + mdio { + pins = "gpio69"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + button_pins: button_pins { + wps_button { + pins = "gpio57"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + + rst_button { + pins = "gpio67"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + qpic_pins: qpic-pins { + status = "disabled"; + /*disabling qpic-pins due to the leds are using all of the gpios*/ + }; + + leds_pins: leds_pinmux { + led_pwr_green { + pins = "gpio4"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_system_green { + pins = "gpio1"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_system_red { + pins = "gpio3"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan1_green { + pins = "gpio6"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan1_amber { + pins = "gpio7"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan2_green { + pins = "gpio8"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan2_amber { + pins = "gpio11"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan3_green { + pins = "gpio12"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan3_amber { + pins = "gpio13"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan4_green { + pins = "gpio14"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_lan4_amber { + pins = "gpio15"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_1_green { + pins = "gpio54"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_1_amber { + pins = "gpio56"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_2_green { + pins = "gpio51"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_10g_2_amber { + pins = "gpio52"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + led_wifi_green { + pins = "gpio42"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; +}; + +&blsp1_uart5 { + status = "okay"; +}; + +&prng { + status = "okay"; +}; + +&ssphy_0 { + status = "okay"; +}; + +&qusb_phy_0 { + status = "okay"; +}; + +&ssphy_1 { + status = "okay"; +}; + +&qusb_phy_1 { + status = "okay"; +}; + +&usb_0 { + status = "okay"; +}; + +&usb_1 { + status = "okay"; +}; + +&cryptobam { + status = "okay"; +}; + +&crypto { + status = "okay"; +}; + +&qpic_bam { + status = "okay"; +}; + +&blsp1_spi1 { /* BLSP1 QUP1 */ + pinctrl-0 = <&spi_0_pins>; + pinctrl-names = "default"; + cs-gpios = <0>; + status = "okay"; + + flash@0 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + compatible = "jedec,spi-nor"; + spi-max-frequency = <50000000>; + + partitions { + compatible = "qcom,smem-part"; + }; + }; +}; + +&mdio { + status = "okay"; + + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + reset-gpios = <&tlmm 37 GPIO_ACTIVE_LOW>; + + ethernet-phy@0 { + compatible ="ethernet-phy-ieee802.3-c45"; + reg = <0>; + reset-gpios = <&tlmm 59 GPIO_ACTIVE_LOW>; + }; + + ethernet-phy@8 { + compatible ="ethernet-phy-ieee802.3-c45"; + reg = <8>; + reset-gpios = <&tlmm 44 GPIO_ACTIVE_LOW>; + }; + + ethernet-phy@16 { + reg = <16>; + }; + + ethernet-phy@17 { + reg = <17>; + }; + + ethernet-phy@18 { + reg = <18>; + }; + + ethernet-phy@19 { + reg = <19>; + }; + +}; + +&sdhc_1 { + /* According to the stock dts from the QNAP gpl drop + * the emmc has a problem with the hs400 > hs200 speed switch. + * But at the momment it doesn't seems possible to run that card in hs200 mode, + * it doesn't get recognized without the hs400 mode. + * Unfortunately it's not stable in hs400 mode so reduce the speed to mmc highspeed. + */ + no-1-8-v; + cap-sd-highspeed; + cap-mmc-highspeed; + max-frequency = <5000000>; + vqmmc-supply = <&ldo11>; + status = "okay"; +}; + +&ess_switch { + status = "okay"; + + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x3e>; /* lan port bitmap */ + switch_wan_bmp = <0xc0>; /* wan port bitmap */ + switch_mac_mode = <0x0>; /* mac mode for uniphy instance0*/ + switch_mac_mode1 = <0xd>; /* mac mode for uniphy instance1*/ + switch_mac_mode2 = <0xd>; /* mac mode for uniphy instance2*/ + bm_tick_mode = <0>; /* bm tick mode */ + tm_tick_mode = <0>; /* tm tick mode */ + + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <16>; + }; + port@1 { + port_id = <2>; + phy_address = <17>; + }; + port@2 { + port_id = <3>; + phy_address = <18>; + }; + port@3 { + port_id = <4>; + phy_address = <19>; + }; + port@4 { + port_id = <5>; + phy_address = <8>; + compatible = "ethernet-phy-ieee802.3-c45"; + ethernet-phy-ieee802.3-c45; + }; + port@5 { + port_id = <6>; + phy_address = <0>; + compatible = "ethernet-phy-ieee802.3-c45"; + ethernet-phy-ieee802.3-c45; + }; + }; + + port_scheduler_resource { + port@0 { + port_id = <0>; + ucast_queue = <0 143>; + mcast_queue = <256 271>; + l0sp = <0 35>; + l0cdrr = <0 47>; + l0edrr = <0 47>; + l1cdrr = <0 7>; + l1edrr = <0 7>; + }; + port@1 { + port_id = <1>; + ucast_queue = <144 159>; + mcast_queue = <272 275>; + l0sp = <36 39>; + l0cdrr = <48 63>; + l0edrr = <48 63>; + l1cdrr = <8 11>; + l1edrr = <8 11>; + }; + port@2 { + port_id = <2>; + ucast_queue = <160 175>; + mcast_queue = <276 279>; + l0sp = <40 43>; + l0cdrr = <64 79>; + l0edrr = <64 79>; + l1cdrr = <12 15>; + l1edrr = <12 15>; + }; + port@3 { + port_id = <3>; + ucast_queue = <176 191>; + mcast_queue = <280 283>; + l0sp = <44 47>; + l0cdrr = <80 95>; + l0edrr = <80 95>; + l1cdrr = <16 19>; + l1edrr = <16 19>; + }; + port@4 { + port_id = <4>; + ucast_queue = <192 207>; + mcast_queue = <284 287>; + l0sp = <48 51>; + l0cdrr = <96 111>; + l0edrr = <96 111>; + l1cdrr = <20 23>; + l1edrr = <20 23>; + }; + port@5 { + port_id = <5>; + ucast_queue = <208 223>; + mcast_queue = <288 291>; + l0sp = <52 55>; + l0cdrr = <112 127>; + l0edrr = <112 127>; + l1cdrr = <24 27>; + l1edrr = <24 27>; + }; + port@6 { + port_id = <6>; + ucast_queue = <224 239>; + mcast_queue = <292 295>; + l0sp = <56 59>; + l0cdrr = <128 143>; + l0edrr = <128 143>; + l1cdrr = <28 31>; + l1edrr = <28 31>; + }; + port@7 { + port_id = <7>; + ucast_queue = <240 255>; + mcast_queue = <296 299>; + l0sp = <60 63>; + l0cdrr = <144 159>; + l0edrr = <144 159>; + l1cdrr = <32 35>; + l1edrr = <32 35>; + }; + }; + port_scheduler_config { + port@0 { + port_id = <0>; + l1scheduler { + group@0 { + sp = <0 1>; /*L0 SPs*/ + /*cpri cdrr epri edrr*/ + cfg = <0 0 0 0>; + }; + }; + l0scheduler { + group@0 { + /*unicast queues*/ + ucast_queue = <0 4 8>; + /*multicast queues*/ + mcast_queue = <256 260>; + /*sp cpri cdrr epri edrr*/ + cfg = <0 0 0 0 0>; + }; + group@1 { + ucast_queue = <1 5 9>; + mcast_queue = <257 261>; + cfg = <0 1 1 1 1>; + }; + group@2 { + ucast_queue = <2 6 10>; + mcast_queue = <258 262>; + cfg = <0 2 2 2 2>; + }; + group@3 { + ucast_queue = <3 7 11>; + mcast_queue = <259 263>; + cfg = <0 3 3 3 3>; + }; + }; + }; + port@1 { + port_id = <1>; + l1scheduler { + group@0 { + sp = <36>; + cfg = <0 8 0 8>; + }; + group@1 { + sp = <37>; + cfg = <1 9 1 9>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <144>; + ucast_loop_pri = <16>; + mcast_queue = <272>; + mcast_loop_pri = <4>; + cfg = <36 0 48 0 48>; + }; + }; + }; + port@2 { + port_id = <2>; + l1scheduler { + group@0 { + sp = <40>; + cfg = <0 12 0 12>; + }; + group@1 { + sp = <41>; + cfg = <1 13 1 13>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <160>; + ucast_loop_pri = <16>; + mcast_queue = <276>; + mcast_loop_pri = <4>; + cfg = <40 0 64 0 64>; + }; + }; + }; + port@3 { + port_id = <3>; + l1scheduler { + group@0 { + sp = <44>; + cfg = <0 16 0 16>; + }; + group@1 { + sp = <45>; + cfg = <1 17 1 17>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <176>; + ucast_loop_pri = <16>; + mcast_queue = <280>; + mcast_loop_pri = <4>; + cfg = <44 0 80 0 80>; + }; + }; + }; + port@4 { + port_id = <4>; + l1scheduler { + group@0 { + sp = <48>; + cfg = <0 20 0 20>; + }; + group@1 { + sp = <49>; + cfg = <1 21 1 21>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <192>; + ucast_loop_pri = <16>; + mcast_queue = <284>; + mcast_loop_pri = <4>; + cfg = <48 0 96 0 96>; + }; + }; + }; + port@5 { + port_id = <5>; + l1scheduler { + group@0 { + sp = <52>; + cfg = <0 24 0 24>; + }; + group@1 { + sp = <53>; + cfg = <1 25 1 25>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <208>; + ucast_loop_pri = <16>; + mcast_queue = <288>; + mcast_loop_pri = <4>; + cfg = <52 0 112 0 112>; + }; + }; + }; + port@6 { + port_id = <6>; + l1scheduler { + group@0 { + sp = <56>; + cfg = <0 28 0 28>; + }; + group@1 { + sp = <57>; + cfg = <1 29 1 29>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <224>; + ucast_loop_pri = <16>; + mcast_queue = <292>; + mcast_loop_pri = <4>; + cfg = <56 0 128 0 128>; + }; + }; + }; + port@7 { + port_id = <7>; + l1scheduler { + group@0 { + sp = <60>; + cfg = <0 32 0 32>; + }; + group@1 { + sp = <61>; + cfg = <1 33 1 33>; + }; + }; + l0scheduler { + group@0 { + ucast_queue = <240>; + ucast_loop_pri = <16>; + mcast_queue = <296>; + cfg = <60 0 144 0 144>; + }; + }; + }; + }; +}; + +&soc { + dp1: dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <16>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp2: dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <17>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp3: dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <18>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp4: dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <19>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp5: dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a001800 0x200>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <8>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp6: dp6 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <6>; + reg = <0x3a007000 0x3fff>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <0>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; +}; + +&wifi { + status = "okay"; + /* using board_id 0xff is intentionally + * as the stock firmware is also using this default board_id + */ + qcom,board_id = <0xff>; + qcom,ath11k-calibration-variant = "QNAP-301w"; +}; diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-512m.dtsi b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-512m.dtsi new file mode 100644 index 000000000..dace4008b --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-512m.dtsi @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include "ipq8074.dtsi" + +&tzapp_region { + reg = <0x0 0x4a400000 0x0 0x100000>; +}; + +&q6_region { + reg = <0x0 0x4b000000 0x0 0x3700000>; +}; + +&q6_etr_region { + reg = <0x0 0x4e700000 0x0 0x100000>; +}; + +&m3_dump_region { + reg = <0x0 0x4e800000 0x0 0x100000>; +}; diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi new file mode 100644 index 000000000..4207ece81 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include "ipq8074-cpr-regulator.dtsi" + +&CPU0 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&CPU1 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&CPU2 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&CPU3 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&cpus { + cpu_opp_table: cpu_opp_table { + compatible = "operating-points-v2"; + opp-shared; + + opp-1017600000 { + opp-hz = /bits/ 64 <1017600000>; + opp-microvolt = <1>; + clock-latency-ns = <200000>; + }; + opp-1382400000 { + opp-hz = /bits/ 64 <1382400000>; + opp-microvolt = <2>; + clock-latency-ns = <200000>; + }; + }; +}; + +&cpu0_thermal { + trips { + cpu0_passive: cpu-passive { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu0_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu0_passive>; + 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>; + }; + }; +}; + +&cpu1_thermal { + trips { + cpu1_passive: cpu-passive { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu1_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu1_passive>; + 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>; + }; + }; +}; + +&cpu2_thermal { + trips { + cpu2_passive: cpu-passive { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu2_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu2_passive>; + 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>; + }; + }; +}; + +&cpu3_thermal { + trips { + cpu3_passive: cpu-passive { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu3_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu3_passive>; + 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>; + }; + }; +}; + +&cluster_thermal { + trips { + cluster_passive: cluster-passive { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cluster_crit: cluster_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cluster_passive>; + 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>; + }; + }; +}; diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi new file mode 100644 index 000000000..80a1364d8 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ac-nss.dtsi @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only + +&soc { + dummy_reg: dummy-regulator@0 { + compatible = "regulator-fixed"; + regulator-name = "dummy-reg"; + regulator-min-microvolt = <848000>; + regulator-max-microvolt = <848000>; + regulator-always-on; + regulator-boot-on; + }; + + nss-common { + compatible = "qcom,nss-common"; + reg = <0x01868010 0x1000>; + reg-names = "nss-misc-reset"; + }; + + nss0: nss@40000000 { + compatible = "qcom,nss"; + interrupts = , + , + , + , + , + , + , + , + , + ; + reg = <0x39000000 0x1000>, + <0x38000000 0x30000>, + <0x0b111000 0x1000>; + reg-names = "nphys", "vphys", "qgic-phys"; + clocks = <&gcc GCC_NSS_NOC_CLK>, + <&gcc GCC_NSS_PTP_REF_CLK>, + <&gcc GCC_NSS_CSR_CLK>, <&gcc GCC_NSS_CFG_CLK>, + <&gcc GCC_NSS_IMEM_CLK>, + <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, + <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, + <&gcc GCC_NSSNOC_SNOC_CLK>, + <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, + <&gcc GCC_NSS_CE_AXI_CLK>, + <&gcc GCC_NSS_CE_APB_CLK>, + <&gcc GCC_NSSNOC_CE_AXI_CLK>, + <&gcc GCC_NSSNOC_CE_APB_CLK>, + <&gcc GCC_NSSNOC_UBI0_AHB_CLK>, + <&gcc GCC_UBI0_CORE_CLK>, + <&gcc GCC_UBI0_AHB_CLK>, + <&gcc GCC_UBI0_AXI_CLK>, + <&gcc GCC_UBI0_MPT_CLK>, + <&gcc GCC_UBI0_NC_AXI_CLK>; + clock-names = "nss-noc-clk", "nss-ptp-ref-clk", + "nss-csr-clk", "nss-cfg-clk", + "nss-imem-clk", + "nss-nssnoc-qosgen-ref-clk", + "nss-mem-noc-nss-axi-clk", + "nss-nssnoc-snoc-clk", + "nss-nssnoc-timeout-ref-clk", + "nss-ce-axi-clk", "nss-ce-apb-clk", + "nss-nssnoc-ce-axi-clk", + "nss-nssnoc-ce-apb-clk", + "nss-nssnoc-ahb-clk", + "nss-core-clk", "nss-ahb-clk", + "nss-axi-clk", "nss-mpt-clk", + "nss-nc-axi-clk"; + qcom,id = <0>; + qcom,num-queue = <4>; + qcom,num-irq = <10>; + qcom,num-pri = <4>; + qcom,load-addr = <0x40000000>; + qcom,low-frequency = <187200000>; + qcom,mid-frequency = <748800000>; + qcom,max-frequency = <1689600000>; + npu-supply = <&dummy_reg>; + mx-supply = <&dummy_reg>; + qcom,bridge-enabled; + qcom,ipv4-enabled; + qcom,ipv4-reasm-enabled; + qcom,ipv6-enabled; + qcom,ipv6-reasm-enabled; + qcom,wlanredirect-enabled; + qcom,tun6rd-enabled; + qcom,l2tpv2-enabled; + qcom,gre-enabled; + qcom,gre-redir-enabled; + qcom,gre-redir-mark-enabled; + qcom,map-t-enabled; + qcom,portid-enabled; + qcom,ppe-enabled; + qcom,pppoe-enabled; + qcom,pptp-enabled; + qcom,tunipip6-enabled; + qcom,shaping-enabled; + qcom,wlan-dataplane-offload-enabled; + qcom,vlan-enabled; + qcom,igs-enabled; + qcom,vxlan-enabled; + qcom,match-enabled; + qcom,mirror-enabled; + qcom,udp-st-enabled; + }; + + nss1: nss@40800000 { + compatible = "qcom,nss"; + interrupts = , + , + , + , + , + , + , + , + ; + reg = <0x39400000 0x1000>, + <0x38030000 0x30000>, + <0x0b111000 0x1000>; + reg-names = "nphys", "vphys", "qgic-phys"; + clocks = <&gcc GCC_NSS_NOC_CLK>, + <&gcc GCC_NSS_PTP_REF_CLK>, + <&gcc GCC_NSS_CSR_CLK>, <&gcc GCC_NSS_CFG_CLK>, + <&gcc GCC_NSS_IMEM_CLK>, + <&gcc GCC_NSSNOC_QOSGEN_REF_CLK>, + <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, + <&gcc GCC_NSSNOC_SNOC_CLK>, + <&gcc GCC_NSSNOC_TIMEOUT_REF_CLK>, + <&gcc GCC_NSS_CE_AXI_CLK>, + <&gcc GCC_NSS_CE_APB_CLK>, + <&gcc GCC_NSSNOC_CE_AXI_CLK>, + <&gcc GCC_NSSNOC_CE_APB_CLK>, + <&gcc GCC_NSSNOC_UBI1_AHB_CLK>, + <&gcc GCC_UBI1_CORE_CLK>, + <&gcc GCC_UBI1_AHB_CLK>, + <&gcc GCC_UBI1_AXI_CLK>, + <&gcc GCC_UBI1_MPT_CLK>, + <&gcc GCC_UBI1_NC_AXI_CLK>; + clock-names = "nss-noc-clk", "nss-ptp-ref-clk", + "nss-csr-clk", "nss-cfg-clk", + "nss-imem-clk", + "nss-nssnoc-qosgen-ref-clk", + "nss-mem-noc-nss-axi-clk", + "nss-nssnoc-snoc-clk", + "nss-nssnoc-timeout-ref-clk", + "nss-ce-axi-clk", "nss-ce-apb-clk", + "nss-nssnoc-ce-axi-clk", + "nss-nssnoc-ce-apb-clk", + "nss-nssnoc-ahb-clk", + "nss-core-clk", "nss-ahb-clk", + "nss-axi-clk", "nss-mpt-clk", + "nss-nc-axi-clk"; + qcom,id = <1>; + qcom,num-queue = <4>; + qcom,num-irq = <9>; + qcom,num-pri = <4>; + qcom,load-addr = <0x40800000>; + qcom,capwap-enabled; + qcom,dtls-enabled; + qcom,tls-enabled; + qcom,crypto-enabled; + qcom,ipsec-enabled; + qcom,qvpn-enabled; + qcom,pvxlan-enabled; + qcom,clmap-enabled; + qcom,rmnet_rx-enabled; + }; + + nss_crypto: qcom,nss_crypto { + compatible = "qcom,nss-crypto"; + #address-cells = <1>; + #size-cells = <1>; + qcom,max-contexts = <64>; + qcom,max-context-size = <32>; + ranges; + + eip197_node { + compatible = "qcom,eip197"; + reg-names = "crypto_pbase"; + reg = <0x39800000 0x7ffff>; + clocks = <&gcc GCC_NSS_CRYPTO_CLK>, + <&gcc GCC_NSSNOC_CRYPTO_CLK>, + <&gcc GCC_CRYPTO_PPE_CLK>; + clock-names = "crypto_clk", + "crypto_nocclk", + "crypto_ppeclk"; + clock-frequency = /bits/ 64 <600000000 600000000 300000000>; + qcom,dma-mask = <0xff>; + qcom,transform-enabled; + qcom,aes128-cbc; + qcom,aes192-cbc; + qcom,aes256-cbc; + qcom,aes128-ctr; + qcom,aes192-ctr; + qcom,aes256-ctr; + qcom,aes128-ecb; + qcom,aes192-ecb; + qcom,aes256-ecb; + qcom,3des-cbc; + qcom,md5-hash; + qcom,sha160-hash; + qcom,sha224-hash; + qcom,sha384-hash; + qcom,sha512-hash; + qcom,sha256-hash; + qcom,md5-hmac; + qcom,sha160-hmac; + qcom,sha224-hmac; + qcom,sha256-hmac; + qcom,sha384-hmac; + qcom,sha512-hmac; + qcom,aes128-gcm-gmac; + qcom,aes192-gcm-gmac; + qcom,aes256-gcm-gmac; + qcom,aes128-cbc-md5-hmac; + qcom,aes128-cbc-sha160-hmac; + qcom,aes192-cbc-md5-hmac; + qcom,aes192-cbc-sha160-hmac; + qcom,aes256-cbc-md5-hmac; + qcom,aes256-cbc-sha160-hmac; + qcom,aes128-ctr-sha160-hmac; + qcom,aes192-ctr-sha160-hmac; + qcom,aes256-ctr-sha160-hmac; + qcom,aes128-ctr-md5-hmac; + qcom,aes192-ctr-md5-hmac; + qcom,aes256-ctr-md5-hmac; + qcom,3des-cbc-md5-hmac; + qcom,3des-cbc-sha160-hmac; + qcom,aes128-cbc-sha256-hmac; + qcom,aes192-cbc-sha256-hmac; + qcom,aes256-cbc-sha256-hmac; + qcom,aes128-ctr-sha256-hmac; + qcom,aes192-ctr-sha256-hmac; + qcom,aes256-ctr-sha256-hmac; + qcom,3des-cbc-sha256-hmac; + qcom,aes128-cbc-sha384-hmac; + qcom,aes192-cbc-sha384-hmac; + qcom,aes256-cbc-sha384-hmac; + qcom,aes128-ctr-sha384-hmac; + qcom,aes192-ctr-sha384-hmac; + qcom,aes256-ctr-sha384-hmac; + qcom,aes128-cbc-sha512-hmac; + qcom,aes192-cbc-sha512-hmac; + qcom,aes256-cbc-sha512-hmac; + qcom,aes128-ctr-sha512-hmac; + qcom,aes192-ctr-sha512-hmac; + qcom,aes256-ctr-sha512-hmac; + + engine0 { + reg_offset = <0x80000>; + qcom,ifpp-enabled; + qcom,ipue-enabled; + qcom,ofpp-enabled; + qcom,opue-enabled; + }; + }; + }; +}; + + diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-cpr-regulator.dtsi b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-cpr-regulator.dtsi new file mode 100644 index 000000000..e692cc942 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-cpr-regulator.dtsi @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-only + +&soc { + apc_apm: apm@b111000 { + compatible = "qcom,ipq807x-apm"; + reg = <0xb111000 0x1000>; + reg-names = "pm-apcc-glb"; + qcom,apm-post-halt-delay = <0x2>; + qcom,apm-halt-clk-delay = <0x11>; + qcom,apm-resume-clk-delay = <0x10>; + qcom,apm-sel-switch-delay = <0x01>; + }; + + apc_cpr: cpr4-ctrl@b018000 { + compatible = "qcom,cpr4-ipq807x-apss-regulator"; + reg = <0xb018000 0x4000>, <0xa4000 0x1000>, <0x0193d008 0x4>; + reg-names = "cpr_ctrl", "fuse_base", "cpr_tcsr_reg"; + interrupts = ; + interrupt-names = "cpr"; + qcom,cpr-ctrl-name = "apc"; + qcom,cpr-sensor-time = <1000>; + qcom,cpr-loop-time = <5000000>; + qcom,cpr-idle-cycles = <15>; + qcom,cpr-step-quot-init-min = <12>; + qcom,cpr-step-quot-init-max = <14>; + qcom,cpr-count-mode = <0>; /* All-at-once */ + qcom,cpr-count-repeat = <14>; + qcom,cpr-down-error-step-limit = <1>; + qcom,cpr-up-error-step-limit = <1>; + qcom,apm-ctrl = <&apc_apm>; + qcom,apm-threshold-voltage = <848000>; + vdd-supply = <&s3>; + qcom,voltage-step = <8000>; + + thread@0 { + qcom,cpr-thread-id = <0>; + qcom,cpr-consecutive-up = <0>; + qcom,cpr-consecutive-down = <0>; + qcom,cpr-up-threshold = <4>; + qcom,cpr-down-threshold = <1>; + + apc_vreg: regulator { + regulator-name = "apc_corner"; + regulator-min-microvolt = <1>; + regulator-max-microvolt = <6>; + qcom,cpr-part-types = <2>; + qcom,cpr-parts-voltage = <1048000>; + qcom,cpr-parts-voltage-v2 = <992000>; + qcom,cpr-fuse-corners = <4>; + qcom,cpr-fuse-combos = <8>; + qcom,cpr-corners = <6>; + qcom,cpr-speed-bins = <1>; + qcom,cpr-speed-bin-corners = <6>; + qcom,cpr-corner-fmax-map = <1 3 5 6>; + qcom,allow-voltage-interpolation; + qcom,allow-quotient-interpolation; + qcom,cpr-scaled-open-loop-voltage-as-ceiling; + qcom,cpr-voltage-ceiling = + <840000 904000 944000 + 984000 992000 1064000>; + qcom,cpr-voltage-floor = + <592000 648000 712000 + 744000 784000 848000>; + qcom,corner-frequencies = + <1017600000 1382400000 1651200000 + 1843200000 1920000000 2208000000>; + + /* TT/FF parts i.e. turbo L1 OL voltage < 1048 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-0 = + /* Speed bin 0; CPR rev 0..7 */ + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 12000>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>; + + /* SS parts i.e turbo L1 OL voltage >= 1048 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-1 = + /* Speed bin 0; CPR rev 0..7 */ + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 20000 26000 0 20000>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>; + + /* v2 - FF parts i.e. turbo L1 OL voltage < 992 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-v2-0 = + /* Speed bin 0; CPR rev 0..7 */ + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>; + + /* v2 - SS/TT parts i.e turbo L1 OL voltage >= 992 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-v2-1 = + /* Speed bin 0; CPR rev 0..7 */ + < 0 0 0 0>, + < 0 7000 36000 4000>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>; + + /* v2 - FF parts i.e. turbo L1 OL voltage < 992 mV */ + qcom,cpr-closed-loop-voltage-adjustment-v2-0 = + /* Speed bin 0; CPR rev 0..7 */ + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>; + + /* v2 - SS/TT parts i.e turbo L1 OL voltage >= 992 mV */ + qcom,cpr-closed-loop-voltage-adjustment-v2-1 = + /* Speed bin 0; CPR rev 0..7 */ + < 0 0 0 0>, + < 0 0 19000 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>, + < 0 0 0 0>; + + qcom,cpr-ro-scaling-factor = + < 3970 4150 0 2280 2520 2470 2250 2280 + 2390 2330 2530 2500 850 2900 2510 2170 >, + < 3970 4150 0 2280 2520 2470 2250 2280 + 2390 2330 2530 2500 850 2900 2510 2170 >, + < 3970 4150 0 2280 2520 2470 2250 2280 + 2390 2330 2530 2500 850 2900 2510 2170 >, + < 3970 4150 0 2280 2520 2470 2250 2280 + 2390 2330 2530 2500 850 2900 2510 2170 >; + + qcom,cpr-floor-to-ceiling-max-range = + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>, + < 40000 40000 40000 40000 40000 40000>; + regulator-always-on; + }; + }; + }; + + npu_cpr: npu-cpr { + compatible = "qcom,cpr3-ipq807x-npu-regulator"; + reg = <0xa4000 0x1000>, <0x0193d008 0x4>; + reg-names = "fuse_base", "cpr_tcsr_reg"; + qcom,cpr-ctrl-name = "npu"; + vdd-supply = <&s4>; + qcom,voltage-step = <8000>; + thread@0 { + qcom,cpr-thread-id = <0>; + qcom,cpr-consecutive-up = <0>; + qcom,cpr-consecutive-down = <2>; + qcom,cpr-up-threshold = <2>; + qcom,cpr-down-threshold = <1>; + + npu_vreg: regulator { + regulator-name = "npu_corner"; + regulator-min-microvolt = <1>; + regulator-max-microvolt = <3>; + qcom,cpr-part-types = <2>; + qcom,cpr-parts-voltage = <968000>; + qcom,cpr-parts-voltage-v2 = <832001>; + qcom,cpr-cold-temp-threshold-v2 = <30>; + qcom,cpr-fuse-corners = <2>; + qcom,cpr-fuse-combos = <1>; + qcom,cpr-corners = <2>; + qcom,cpr-speed-bins = <1>; + qcom,cpr-speed-bin-corners = <2>; + qcom,allow-voltage-interpolation; + qcom,cpr-corner-fmax-map = <1 2>; + qcom,cpr-voltage-ceiling = + <912000 992000>; + qcom,cpr-voltage-floor = + <752000 792000>; + qcom,corner-frequencies = + <1497600000 1689600000>; + + /* TT/FF parts i.e. turbo OL voltage < 968 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-0 = + < 40000 40000>; + + /* SS parts i.e turbo OL voltage >= 968 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-1 = + < 24000 24000>; + + /* FF parts i.e. turbo OL voltage <= 832 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-v2-0= + <40000 40000>; + + /* TT/SS parts i.e turbo OL voltage > 832 mV */ + qcom,cpr-open-loop-voltage-fuse-adjustment-v2-1= + <40000 40000>; + + /* FF parts i.e. turbo OL voltage <= 832 mV */ + qcom,cpr-cold-temp-voltage-adjustment-v2-0 = + <0 0>; + + /* TT/SS parts i.e turbo OL voltage > 832 mV */ + qcom,cpr-cold-temp-voltage-adjustment-v2-1 = + <35000 27000>; + }; + }; + }; +}; diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ess.dtsi b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ess.dtsi new file mode 100644 index 000000000..7fb30dbdd --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-ess.dtsi @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only + +&clocks { + bias_pll_cc_clk { + compatible = "fixed-clock"; + clock-frequency = <300000000>; + #clock-cells = <0>; + }; + + bias_pll_nss_noc_clk { + compatible = "fixed-clock"; + clock-frequency = <416500000>; + #clock-cells = <0>; + }; +}; + +&soc { + switch: ess-switch@3a000000 { + compatible = "qcom,ess-switch-ipq807x"; + reg = <0x3a000000 0x1000000>; + switch_access_mode = "local bus"; + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_inner_bmp = <0x80>; /*inner port bitmap*/ + clocks = <&gcc GCC_CMN_12GPLL_AHB_CLK>, + <&gcc GCC_CMN_12GPLL_SYS_CLK>, + <&gcc GCC_UNIPHY0_AHB_CLK>, + <&gcc GCC_UNIPHY0_SYS_CLK>, + <&gcc GCC_UNIPHY1_AHB_CLK>, + <&gcc GCC_UNIPHY1_SYS_CLK>, + <&gcc GCC_UNIPHY2_AHB_CLK>, + <&gcc GCC_UNIPHY2_SYS_CLK>, + <&gcc GCC_PORT1_MAC_CLK>, + <&gcc GCC_PORT2_MAC_CLK>, + <&gcc GCC_PORT3_MAC_CLK>, + <&gcc GCC_PORT4_MAC_CLK>, + <&gcc GCC_PORT5_MAC_CLK>, + <&gcc GCC_PORT6_MAC_CLK>, + <&gcc GCC_NSS_PPE_CLK>, + <&gcc GCC_NSS_PPE_CFG_CLK>, + <&gcc GCC_NSSNOC_PPE_CLK>, + <&gcc GCC_NSSNOC_PPE_CFG_CLK>, + <&gcc GCC_NSS_EDMA_CLK>, + <&gcc GCC_NSS_EDMA_CFG_CLK>, + <&gcc GCC_NSS_PPE_IPE_CLK>, + <&gcc GCC_NSS_PPE_BTQ_CLK>, + <&gcc GCC_MDIO_AHB_CLK>, + <&gcc GCC_NSS_NOC_CLK>, + <&gcc GCC_NSSNOC_SNOC_CLK>, + <&gcc GCC_MEM_NOC_NSS_AXI_CLK>, + <&gcc GCC_NSS_CRYPTO_CLK>, + <&gcc GCC_NSS_IMEM_CLK>, + <&gcc GCC_NSS_PTP_REF_CLK>, + <&gcc GCC_NSS_PORT1_RX_CLK>, + <&gcc GCC_NSS_PORT1_TX_CLK>, + <&gcc GCC_NSS_PORT2_RX_CLK>, + <&gcc GCC_NSS_PORT2_TX_CLK>, + <&gcc GCC_NSS_PORT3_RX_CLK>, + <&gcc GCC_NSS_PORT3_TX_CLK>, + <&gcc GCC_NSS_PORT4_RX_CLK>, + <&gcc GCC_NSS_PORT4_TX_CLK>, + <&gcc GCC_NSS_PORT5_RX_CLK>, + <&gcc GCC_NSS_PORT5_TX_CLK>, + <&gcc GCC_NSS_PORT6_RX_CLK>, + <&gcc GCC_NSS_PORT6_TX_CLK>, + <&gcc GCC_UNIPHY0_PORT1_RX_CLK>, + <&gcc GCC_UNIPHY0_PORT1_TX_CLK>, + <&gcc GCC_UNIPHY0_PORT2_RX_CLK>, + <&gcc GCC_UNIPHY0_PORT2_TX_CLK>, + <&gcc GCC_UNIPHY0_PORT3_RX_CLK>, + <&gcc GCC_UNIPHY0_PORT3_TX_CLK>, + <&gcc GCC_UNIPHY0_PORT4_RX_CLK>, + <&gcc GCC_UNIPHY0_PORT4_TX_CLK>, + <&gcc GCC_UNIPHY0_PORT5_RX_CLK>, + <&gcc GCC_UNIPHY0_PORT5_TX_CLK>, + <&gcc GCC_UNIPHY1_PORT5_RX_CLK>, + <&gcc GCC_UNIPHY1_PORT5_TX_CLK>, + <&gcc GCC_UNIPHY2_PORT6_RX_CLK>, + <&gcc GCC_UNIPHY2_PORT6_TX_CLK>, + <&gcc NSS_PORT5_RX_CLK_SRC>, + <&gcc NSS_PORT5_TX_CLK_SRC>; + clock-names = "cmn_ahb_clk", "cmn_sys_clk", + "uniphy0_ahb_clk", "uniphy0_sys_clk", + "uniphy1_ahb_clk", "uniphy1_sys_clk", + "uniphy2_ahb_clk", "uniphy2_sys_clk", + "port1_mac_clk", "port2_mac_clk", + "port3_mac_clk", "port4_mac_clk", + "port5_mac_clk", "port6_mac_clk", + "nss_ppe_clk", "nss_ppe_cfg_clk", + "nssnoc_ppe_clk", "nssnoc_ppe_cfg_clk", + "nss_edma_clk", "nss_edma_cfg_clk", + "nss_ppe_ipe_clk", "nss_ppe_btq_clk", + "gcc_mdio_ahb_clk", "gcc_nss_noc_clk", + "gcc_nssnoc_snoc_clk", + "gcc_mem_noc_nss_axi_clk", + "gcc_nss_crypto_clk", + "gcc_nss_imem_clk", + "gcc_nss_ptp_ref_clk", + "nss_port1_rx_clk", "nss_port1_tx_clk", + "nss_port2_rx_clk", "nss_port2_tx_clk", + "nss_port3_rx_clk", "nss_port3_tx_clk", + "nss_port4_rx_clk", "nss_port4_tx_clk", + "nss_port5_rx_clk", "nss_port5_tx_clk", + "nss_port6_rx_clk", "nss_port6_tx_clk", + "uniphy0_port1_rx_clk", + "uniphy0_port1_tx_clk", + "uniphy0_port2_rx_clk", + "uniphy0_port2_tx_clk", + "uniphy0_port3_rx_clk", + "uniphy0_port3_tx_clk", + "uniphy0_port4_rx_clk", + "uniphy0_port4_tx_clk", + "uniphy0_port5_rx_clk", + "uniphy0_port5_tx_clk", + "uniphy1_port5_rx_clk", + "uniphy1_port5_tx_clk", + "uniphy2_port6_rx_clk", + "uniphy2_port6_tx_clk", + "nss_port5_rx_clk_src", + "nss_port5_tx_clk_src"; + resets = <&gcc GCC_PPE_FULL_RESET>, + <&gcc GCC_UNIPHY0_SOFT_RESET>, + <&gcc GCC_UNIPHY0_XPCS_RESET>, + <&gcc GCC_UNIPHY1_SOFT_RESET>, + <&gcc GCC_UNIPHY1_XPCS_RESET>, + <&gcc GCC_UNIPHY2_SOFT_RESET>, + <&gcc GCC_UNIPHY2_XPCS_RESET>, + <&gcc GCC_NSSPORT1_RESET>, + <&gcc GCC_NSSPORT2_RESET>, + <&gcc GCC_NSSPORT3_RESET>, + <&gcc GCC_NSSPORT4_RESET>, + <&gcc GCC_NSSPORT5_RESET>, + <&gcc GCC_NSSPORT6_RESET>; + reset-names = "ppe_rst", "uniphy0_soft_rst", + "uniphy0_xpcs_rst", "uniphy1_soft_rst", + "uniphy1_xpcs_rst", "uniphy2_soft_rst", + "uniphy2_xpcs_rst", "nss_port1_rst", + "nss_port2_rst", "nss_port3_rst", + "nss_port4_rst", "nss_port5_rst", + "nss_port6_rst"; + mdio-bus = <&mdio>; + status = "disabled"; + }; + + ess-uniphy@7a00000 { + compatible = "qcom,ess-uniphy"; + reg = <0x7a00000 0x30000>; + uniphy_access_mode = "local bus"; + }; + + edma: edma@3ab00000 { + compatible = "qcom,edma"; + reg = <0x3ab00000 0x76900>; + reg-names = "edma-reg-base"; + qcom,txdesc-ring-start = <23>; + qcom,txdesc-rings = <1>; + qcom,txcmpl-ring-start = <7>; + qcom,txcmpl-rings = <1>; + qcom,rxfill-ring-start = <7>; + qcom,rxfill-rings = <1>; + qcom,rxdesc-ring-start = <15>; + qcom,rxdesc-rings = <1>; + interrupts = , + , + , + ; + resets = <&gcc GCC_EDMA_HW_RESET>; + reset-names = "edma_rst"; + status = "disabled"; + }; +}; diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi new file mode 100644 index 000000000..861460c93 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8074-hk-cpu.dtsi @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include "ipq8074-cpr-regulator.dtsi" + +&CPU0 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&CPU1 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&CPU2 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&CPU3 { + cpu-supply = <&apc_vreg>; + operating-points-v2 = <&cpu_opp_table>; + voltage-tolerance = <1>; +}; + +&cpus { + cpu_opp_table: cpu_opp_table { + compatible = "operating-points-v2"; + opp-shared; + + opp-1017600000 { + opp-hz = /bits/ 64 <1017600000>; + opp-microvolt = <1>; + clock-latency-ns = <200000>; + }; + opp-1382400000 { + opp-hz = /bits/ 64 <1382400000>; + opp-microvolt = <2>; + clock-latency-ns = <200000>; + }; + opp-1651200000 { + opp-hz = /bits/ 64 <1651200000>; + opp-microvolt = <3>; + clock-latency-ns = <200000>; + }; + opp-1843200000 { + opp-hz = /bits/ 64 <1843200000>; + opp-microvolt = <4>; + clock-latency-ns = <200000>; + }; + opp-1920000000 { + opp-hz = /bits/ 64 <1920000000>; + opp-microvolt = <5>; + clock-latency-ns = <200000>; + }; + opp-2208000000 { + opp-hz = /bits/ 64 <2208000000>; + opp-microvolt = <6>; + clock-latency-ns = <200000>; + }; + }; +}; + +&cpu0_thermal { + trips { + cpu0_passive_low: cpu-passive-low { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu0_passive_high: cpu-passive-high { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu0_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu0_passive_low>; + 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>; + }; + map1 { + trip = <&cpu0_passive_high>; + 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>; + }; + }; +}; + +&cpu1_thermal { + trips { + cpu1_passive_low: cpu-passive-low { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu1_passive_high: cpu-passive-high { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu1_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu1_passive_low>; + 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>; + }; + map1 { + trip = <&cpu1_passive_high>; + 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>; + }; + }; +}; + +&cpu2_thermal { + trips { + cpu2_passive_low: cpu-passive-low { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu2_passive_high: cpu-passive-high { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu2_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu2_passive_low>; + 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>; + }; + map1 { + trip = <&cpu2_passive_high>; + 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>; + }; + }; +}; + +&cpu3_thermal { + trips { + cpu3_passive_low: cpu-passive-low { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu3_passive_high: cpu-passive-high { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + + cpu3_crit: cpu_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cpu3_passive_low>; + 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>; + }; + map1 { + trip = <&cpu3_passive_high>; + 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>; + }; + }; +}; + +&cluster_thermal { + trips { + cluster_passive_low: cluster-passive { + temperature = <95000>; + hysteresis = <2000>; + type = "passive"; + }; + + cluster_passive_high: cluster-passive-high { + temperature = <100000>; + hysteresis = <2000>; + type = "passive"; + }; + + cluster_crit: cluster_crit { + temperature = <110000>; + hysteresis = <1000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&cluster_passive_low>; + 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>; + }; + map1 { + trip = <&cluster_passive_high>; + 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>; + }; + }; +}; diff --git a/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts new file mode 100755 index 000000000..9b2ef3875 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/arch/arm64/boot/dts/qcom/ipq8078-xtr10890.dts @@ -0,0 +1,969 @@ +// SPDX-License-Identifier: GPL-2.0-only +/dts-v1/; +/* Copyright (c) 2017, 2020-2021, The Linux Foundation. All rights reserved. + */ +#include "ipq8074.dtsi" +#include "ipq8074-hk-cpu.dtsi" +#include "ipq8074-ac-nss.dtsi" +#include +#include +#include + +/ { + #address-cells = <0x2>; + #size-cells = <0x2>; + model = "TP-LINK XTR10890"; + compatible = "tplink,xtr10890", "qcom,ipq8074"; + interrupt-parent = <&intc>; + //qcom,msm-id = <0x143 0x0>, <0x186 0x0>, <0x158 0x0>, <0x188 0x0>; + + aliases { + serial0 = &blsp1_uart5; + serial1 = &blsp1_uart3; + /* + * Aliases as required by u-boot + * to patch MAC addresses + */ + led-boot = &led_system_red; + led-failsafe = &led_system_green; + led-running = &led_system_red; + led-upgrade = &led_system_green; + ethernet0 = &dp1; + ethernet1 = &dp2; + ethernet2 = &dp3; + ethernet3 = &dp4; + ethernet4 = &dp5; + ethernet5 = &dp6; + }; + + chosen { + stdout-path = "serial0"; + bootargs-append = " root=/dev/ubiblock0_1 rootfstype=squashfs"; + }; + + reserved-memory { + + /delete-node/ wifi_dump@51100000; + /delete-node/ wigig_dump@51300000; + qcn9000_pcie0: qcn9000_pcie0@51100000 { + no-map; + reg = <0x0 0x51100000 0x0 0x03500000>; + }; + + mhi_region0: dma_pool0@54600000 { + compatible = "shared-dma-pool"; + no-map; + reg = <0x0 0x54600000 0x0 0x00900000>; + }; + + }; + + + keys { + compatible = "gpio-keys"; + pinctrl-0 = <&button_pins>; + pinctrl-names = "default"; + status = "okay"; + button@1 { + label = "wps"; + linux,code = ; + gpios = <&tlmm 50 GPIO_ACTIVE_LOW>; + linux,input-type = <1>; + debounce-interval = <60>; + }; + }; + + leds { + compatible = "gpio-leds"; + led_system_green: system-green { + label = "green:system"; + gpios = <&tlmm 31 GPIO_ACTIVE_LOW>; + }; + led_system_red: system-red { + label = "red:system"; + gpios = <&tlmm 29 GPIO_ACTIVE_HIGH>; + }; + + 10g-green { + label = "green:10g"; + gpios = <&tlmm 32 GPIO_ACTIVE_HIGH>; + }; + + sfp_green { + label = "green:sfp"; + gpios = <&tlmm 30 GPIO_ACTIVE_HIGH>; + }; + }; + +}; + +&sdhc_1 { + status = "okay"; +}; + + +&qpic_nand { + pinctrl-0 = <&qpic_pins>; + pinctrl-names = "default"; + status = "okay"; + nand@0 { + reg = <0>; + #address-cells = <1>; + #size-cells = <1>; + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + + partition0@0 { + label = "0:SBL1"; + reg = <0x0 0x80000>; + read-only; + }; + partition1@80000 { + label = "0:MIBIB"; + reg = <0x80000 0x40000>; + }; + + partition2@c0000 { + label = "0:BOOTCONFIG"; + reg = <0xc0000 0x20000>; + read-only; + }; + partition3@e0000 { + label = "0:BOOTCONFIG1"; + reg = <0xe0000 0x20000>; + read-only; + }; + + partition4@100000 { + label = "0:QSEE"; + reg = <0x100000 0x280000>; + read-only; + }; + + partition4@380000 { + label = "0:DEVCFG"; + reg = <0x380000 0x20000>; + read-only; + }; + + partition4@3a0000 { + label = "0:RPM"; + reg = <0x3a0000 0x40000>; + read-only; + }; + + partition5@3e0000 { + label = "0:CDT"; + reg = <0x3e0000 0x20000>; + read-only; + }; + + partition6@400000 { + label = "0:APPSBLENV"; + reg = <0x400000 0x40000>; + }; + + partition7@440000 { + label = "0:APPSBL"; + reg = <0x440000 0x100000>; + }; + + partition8@540000 { + label = "0:ART"; + reg = <0x540000 0x80000>; + }; + + partition12@5c0000 { + label = "rootfs"; + reg = <0x5c0000 0x7000000>; + }; + + partition13@75c0000 { + label = "0:WIFIFW"; + reg = <0x75c0000 0x900000>; + }; + + partition15@7ec0000 { + label = "0:ETHPHYFW"; + reg = <0x7ec0000 0x80000>; + }; + }; +}; + + +&mdio{ + status = "okay"; + + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 37 0>; + phy0: ethernet-phy@0 { + reg = <16>; + }; + phy1: ethernet-phy@1 { + reg = <17>; + }; + phy2: ethernet-phy@2 { + reg = <18>; + }; + phy3: ethernet-phy@3 { + reg = <19>; + }; + phy4: ethernet-phy@4 { + reg = <30>; + }; + phy5: ethernet-phy@5 { + compatible ="ethernet-phy-ieee802.3-c45"; + reg = <0>; + }; +}; + +&tlmm { + sd_pins: sd-pinmux { + pins = "gpio63"; + function = "sd_card"; + drive-strength = <8>; + bias-pull-up; + }; + + button_pins: button_pins { + wps_button { + pins = "gpio50"; + function = "gpio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + leds_pins: leds_pinmux { + led_pwr_green { + pins = "gpio31"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + led_pwr_red { + pins = "gpio29"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + led_sfp_green { + pins = "gpio30"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + + led_10g_green { + pins = "gpio32"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + }; + }; + + mdio_pins: mdio_pinmux { + mux_0 { + pins = "gpio68"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + mux_1 { + pins = "gpio69"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + }; + + qpic_pins: qpic-pins { + pins = "gpio1", "gpio3", "gpio4", + "gpio5", "gpio6", "gpio7", + "gpio8", "gpio10", "gpio11", + "gpio12", "gpio13", "gpio14", + "gpio15", "gpio17"; + function = "qpic"; + drive-strength = <8>; + bias-disable; + }; + + uniphy_pins: uniphy_pinmux { + mux { + pins = "gpio60"; + function = "rx2"; + bias-disable; + }; + + sfp_tx { + pins = "gpio52"; + function = "gpio"; + drive-strength = <8>; + bias-pull-down; + output-low; + }; + }; + + i2c_0_pins: i2c-0-pinmux { + pins = "gpio46", "gpio47"; + function = "blsp1_i2c"; + drive-strength = <8>; + bias-disable; + }; + + pwm_pins: pwm_pinmux { + mux_1 { + pins = "gpio25"; + function = "pwm02"; + drive-strength = <8>; + }; + }; + + pcie0_pins: pcie_pins { + pcie0_rst { + pins = "gpio58"; + function = "pcie0_rst"; + drive-strength = <8>; + bias-pull-down; + }; + pcie0_wake { + pins = "gpio59"; + function = "pcie0_wake"; + drive-strength = <8>; + bias-pull-down; + }; + }; +}; + +&blsp1_uart5 { + status = "okay"; +}; + +&blsp1_spi1 { + status = "okay"; + pinctrl-0 = <&spi_0_pins>; + pinctrl-names = "default"; + cs-select = <0>; + + m25p80@0 { + compatible = "n25q128a11"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; + +&blsp1_i2c2 { + pinctrl-0 = <&i2c_0_pins>; + pinctrl-names = "default"; + status = "okay"; +}; + +&ssphy_0 { + status = "okay"; +}; + +&qusb_phy_0 { + status = "okay"; +}; + +&ssphy_1 { + status = "okay"; +}; + +&qusb_phy_1 { + status = "okay"; +}; + +&usb_0 { + status = "okay"; +}; + +&usb_1 { + status = "okay"; +}; + +&prng { + status = "okay"; +}; + +&cryptobam { + status = "okay"; +}; + +&crypto { + status = "okay"; +}; + +&qpic_bam { + status = "okay"; +}; + +&qmp_pcie_phy0 { + status = "okay"; +}; + +&pcie0 { + perst-gpio = <&tlmm 58 1>; + status = "okay"; + + pcie0_rp { + status = "ok"; + + mhi_0: qcom,mhi@0 { + reg = <0 0 0 0 0 >; + qrtr_instance_id = <0x20>; + #address-cells = <0x2>; + #size-cells = <0x2>; + memory-region = <&mhi_region0>; + // base-addr = <0x51100000>; + // m3-dump-addr = <0x53400000>; + // etr-addr = <0x53500000>; + // qcom,caldb-addr = <0x53600000>; + // mhi,max-channels = <30>; + // mhi,timeout = <10000>; + // qcom,board_id= <0xa4>; + + // pcie0_mhi: pcie0_mhi { + // status = "ok"; + // }; + }; + }; + +}; + +&qmp_pcie_phy1 { + //status = "okay"; + status = "disabled"; +}; + +&pcie1 { + perst-gpio = <&tlmm 61 0x1>; + status = "disabled"; +}; + +&ess_switch { + status = "okay"; + pinctrl-0 = <&uniphy_pins>; + pinctrl-names = "default"; + switch_cpu_bmp = <0x1>; /* cpu port bitmap */ + switch_lan_bmp = <0x3e>; /* lan port bitmap */ + switch_wan_bmp = <0x40>; /* wan port bitmap */ + switch_mac_mode = <0x0b>; /* mac mode for uniphy instance0*/ + switch_mac_mode1 = <0x0e>; /* mac mode for uniphy instance1*/ + switch_mac_mode2 = <0x0d>; /* mac mode for uniphy instance2*/ + bm_tick_mode = <0>; /* bm tick mode */ + tm_tick_mode = <0>; /* tm tick mode */ + qcom,port_phyinfo { + port@0 { + port_id = <1>; + phy_address = <16>; + }; + port@1 { + port_id = <2>; + phy_address = <17>; + }; + port@2 { + port_id = <3>; + phy_address = <18>; + }; + port@3 { + port_id = <4>; + phy_address = <19>; + }; + port@4 { + port_id = <5>; + phy_address = <30>; + phy_i2c_address = <30>; + phy-i2c-mode; /*i2c access phy */ + media-type = "sfp"; /* fiber mode */ + }; + port@5 { + port_id = <6>; + phy_address = <0>; + ethernet-phy-ieee802.3-c45; + }; + }; + port_scheduler_resource { + + port@0 { + port_id = < 0x00 >; + ucast_queue = < 0x00 0x8f >; + mcast_queue = < 0x100 0x10f >; + l0sp = < 0x00 0x23 >; + l0cdrr = < 0x00 0x2f >; + l0edrr = < 0x00 0x2f >; + l1cdrr = < 0x00 0x07 >; + l1edrr = < 0x00 0x07 >; + }; + + port@1 { + port_id = < 0x01 >; + ucast_queue = < 0x90 0x9f >; + mcast_queue = < 0x110 0x113 >; + l0sp = < 0x24 0x27 >; + l0cdrr = < 0x30 0x3f >; + l0edrr = < 0x30 0x3f >; + l1cdrr = < 0x08 0x0b >; + l1edrr = < 0x08 0x0b >; + }; + + port@2 { + port_id = < 0x02 >; + ucast_queue = < 0xa0 0xaf >; + mcast_queue = < 0x114 0x117 >; + l0sp = < 0x28 0x2b >; + l0cdrr = < 0x40 0x4f >; + l0edrr = < 0x40 0x4f >; + l1cdrr = < 0x0c 0x0f >; + l1edrr = < 0x0c 0x0f >; + }; + + port@3 { + port_id = < 0x03 >; + ucast_queue = < 0xb0 0xbf >; + mcast_queue = < 0x118 0x11b >; + l0sp = < 0x2c 0x2f >; + l0cdrr = < 0x50 0x5f >; + l0edrr = < 0x50 0x5f >; + l1cdrr = < 0x10 0x13 >; + l1edrr = < 0x10 0x13 >; + }; + + port@4 { + port_id = < 0x04 >; + ucast_queue = < 0xc0 0xcf >; + mcast_queue = < 0x11c 0x11f >; + l0sp = < 0x30 0x33 >; + l0cdrr = < 0x60 0x6f >; + l0edrr = < 0x60 0x6f >; + l1cdrr = < 0x14 0x17 >; + l1edrr = < 0x14 0x17 >; + }; + + port@5 { + port_id = < 0x05 >; + ucast_queue = < 0xd0 0xdf >; + mcast_queue = < 0x120 0x123 >; + l0sp = < 0x34 0x37 >; + l0cdrr = < 0x70 0x7f >; + l0edrr = < 0x70 0x7f >; + l1cdrr = < 0x18 0x1b >; + l1edrr = < 0x18 0x1b >; + }; + + port@6 { + port_id = < 0x06 >; + ucast_queue = < 0xe0 0xef >; + mcast_queue = < 0x124 0x127 >; + l0sp = < 0x38 0x3b >; + l0cdrr = < 0x80 0x8f >; + l0edrr = < 0x80 0x8f >; + l1cdrr = < 0x1c 0x1f >; + l1edrr = < 0x1c 0x1f >; + }; + + port@7 { + port_id = < 0x07 >; + ucast_queue = < 0xf0 0xff >; + mcast_queue = < 0x128 0x12b >; + l0sp = < 0x3c 0x3f >; + l0cdrr = < 0x90 0x9f >; + l0edrr = < 0x90 0x9f >; + l1cdrr = < 0x20 0x23 >; + l1edrr = < 0x20 0x23 >; + }; + }; + + port_scheduler_config { + + port@0 { + port_id = < 0x00 >; + + l1scheduler { + + group@0 { + sp = < 0x00 0x01 >; + cfg = < 0x00 0x00 0x00 0x00 >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0x00 0x04 0x08 >; + mcast_queue = < 0x100 0x104 >; + cfg = < 0x00 0x00 0x00 0x00 0x00 >; + }; + + group@1 { + ucast_queue = < 0x01 0x05 0x09 >; + mcast_queue = < 0x101 0x105 >; + cfg = < 0x00 0x01 0x01 0x01 0x01 >; + }; + + group@2 { + ucast_queue = < 0x02 0x06 0x0a >; + mcast_queue = < 0x102 0x106 >; + cfg = < 0x00 0x02 0x02 0x02 0x02 >; + }; + + group@3 { + ucast_queue = < 0x03 0x07 0x0b >; + mcast_queue = < 0x103 0x107 >; + cfg = < 0x00 0x03 0x03 0x03 0x03 >; + }; + }; + }; + + port@1 { + port_id = < 0x01 >; + + l1scheduler { + + group@0 { + sp = < 0x24 >; + cfg = < 0x00 0x08 0x00 0x08 >; + }; + + group@1 { + sp = < 0x25 >; + cfg = < 0x01 0x09 0x01 0x09 >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0x90 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x110 >; + mcast_loop_pri = < 0x04 >; + cfg = < 0x24 0x00 0x30 0x00 0x30 >; + }; + }; + }; + + port@2 { + port_id = < 0x02 >; + + l1scheduler { + + group@0 { + sp = < 0x28 >; + cfg = < 0x00 0x0c 0x00 0x0c >; + }; + + group@1 { + sp = < 0x29 >; + cfg = < 0x01 0x0d 0x01 0x0d >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0xa0 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x114 >; + mcast_loop_pri = < 0x04 >; + cfg = < 0x28 0x00 0x40 0x00 0x40 >; + }; + }; + }; + + port@3 { + port_id = < 0x03 >; + + l1scheduler { + + group@0 { + sp = < 0x2c >; + cfg = < 0x00 0x10 0x00 0x10 >; + }; + + group@1 { + sp = < 0x2d >; + cfg = < 0x01 0x11 0x01 0x11 >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0xb0 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x118 >; + mcast_loop_pri = < 0x04 >; + cfg = < 0x2c 0x00 0x50 0x00 0x50 >; + }; + }; + }; + + port@4 { + port_id = < 0x04 >; + + l1scheduler { + + group@0 { + sp = < 0x30 >; + cfg = < 0x00 0x14 0x00 0x14 >; + }; + + group@1 { + sp = < 0x31 >; + cfg = < 0x01 0x15 0x01 0x15 >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0xc0 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x11c >; + mcast_loop_pri = < 0x04 >; + cfg = < 0x30 0x00 0x60 0x00 0x60 >; + }; + }; + }; + + port@5 { + port_id = < 0x05 >; + + l1scheduler { + + group@0 { + sp = < 0x34 >; + cfg = < 0x00 0x18 0x00 0x18 >; + }; + + group@1 { + sp = < 0x35 >; + cfg = < 0x01 0x19 0x01 0x19 >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0xd0 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x120 >; + mcast_loop_pri = < 0x04 >; + cfg = < 0x34 0x00 0x70 0x00 0x70 >; + }; + }; + }; + + port@6 { + port_id = < 0x06 >; + + l1scheduler { + + group@0 { + sp = < 0x38 >; + cfg = < 0x00 0x1c 0x00 0x1c >; + }; + + group@1 { + sp = < 0x39 >; + cfg = < 0x01 0x1d 0x01 0x1d >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0xe0 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x124 >; + mcast_loop_pri = < 0x04 >; + cfg = < 0x38 0x00 0x80 0x00 0x80 >; + }; + }; + }; + + port@7 { + port_id = < 0x07 >; + + l1scheduler { + + group@0 { + sp = < 0x3c >; + cfg = < 0x00 0x20 0x00 0x20 >; + }; + + group@1 { + sp = < 0x3d >; + cfg = < 0x01 0x21 0x01 0x21 >; + }; + }; + + l0scheduler { + + group@0 { + ucast_queue = < 0xf0 >; + ucast_loop_pri = < 0x10 >; + mcast_queue = < 0x128 >; + cfg = < 0x3c 0x00 0x90 0x00 0x90 >; + }; + }; + }; + }; +}; + + +&soc { + + phy@86000 { + status = "okay"; + }; + + sd-pwrseq { + status = "okay"; + }; + + sdhci@7864900 { + pinctrl-0 = <&sd_pins>; + pinctrl-names = "default"; + cd-gpios = <&tlmm 63 GPIO_ACTIVE_LOW>; + status = "okay"; + }; + + pwm { + pinctrl-0 = <&pwm_pins>; + pinctrl-names = "default"; + used-pwm-indices = <1>, <0>, <0>, <0>; + status = "disabled"; + }; + + // qcom,test@0 { + // status = "okay"; + // }; + + + dp1: dp1 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <1>; + reg = <0x3a001000 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <16>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp2: dp2 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <2>; + reg = <0x3a001200 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <17>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp3: dp3 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <3>; + reg = <0x3a001400 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <18>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp4: dp4 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <4>; + reg = <0x3a001600 0x200>; + qcom,mactype = <0>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <19>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp5: dp5 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <5>; + reg = <0x3a003000 0x3fff>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <30>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + dp6: dp6 { + device_type = "network"; + compatible = "qcom,nss-dp"; + qcom,id = <6>; + reg = <0x3a007000 0x3fff>; + qcom,mactype = <1>; + local-mac-address = [000000000000]; + qcom,link-poll = <1>; + qcom,phy-mdio-addr = <0>; + phy-mode = "sgmii"; + mdio-bus = <&mdio>; + }; + + // qcn9000_pcie0: qcn9000_pcie0@51100000 { + // no-map; + // reg = <0x0 0x51100000 0x0 0x02D00000>; + // }; + + wifi1: wifi1@f00000 { + + compatible = "qcom,cnss-qcn9000"; + qcom,wlan-ramdump-dynamic = <0x400000>; + mhi,max-channels = <30>; + mhi,timeout = <10000>; + qrtr_node_id = <0x20>; + qca,auto-restart; + + pcie0_mhi: pcie0_mhi { + status = "ok"; + }; + + }; +}; + +&wifi { + status = "okay"; + qcom,board_id = <528>; + qcom,ath11k-calibration-variant = "TL-XTR10890"; +}; + +&wifi1 { + base-addr = <0x51100000>; + m3-dump-addr = <0x53400000>; + etr-addr = <0x53500000>; + caldb-addr = <0x53600000>; + hremote-size = <0x2300000>; + tgt-mem-mode = <0x0>; + caldb-size = <0x800000>; + hremote_node = <&qcn9000_pcie0>; + pageable-size = <0x800000>; + board_id = <171>; + qcom,ath11k-calibration-variant = "TL-XTR10890"; + status = "ok"; +}; diff --git a/target/linux/ipq807x/files-5.15/drivers/power/qcom/Kconfig b/target/linux/ipq807x/files-5.15/drivers/power/qcom/Kconfig new file mode 100644 index 000000000..01993a617 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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. diff --git a/target/linux/ipq807x/files-5.15/drivers/power/qcom/Makefile b/target/linux/ipq807x/files-5.15/drivers/power/qcom/Makefile new file mode 100644 index 000000000..43a288e62 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/drivers/power/qcom/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_QCOM_APM) += apm.o diff --git a/target/linux/ipq807x/files-5.15/drivers/power/qcom/apm.c b/target/linux/ipq807x/files-5.15/drivers/power/qcom/apm.c new file mode 100644 index 000000000..bd93358e3 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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"); diff --git a/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-npu-regulator.c b/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-npu-regulator.c new file mode 100644 index 000000000..e808073c0 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.c b/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.c new file mode 100644 index 000000000..d5d8a1a0b --- /dev/null +++ b/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.c @@ -0,0 +1,5112 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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, + .llseek = no_llseek, +}; + +/** + * 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; +} diff --git a/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.h b/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-regulator.h new file mode 100644 index 000000000..7c69c4630 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 +#include +#include +#include +#include +#include +#include + +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__ */ diff --git a/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-util.c b/target/linux/ipq807x/files-5.15/drivers/regulator/cpr3-util.c new file mode 100644 index 000000000..45493af71 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; +} diff --git a/target/linux/ipq807x/files-5.15/drivers/regulator/cpr4-apss-regulator.c b/target/linux/ipq807x/files-5.15/drivers/regulator/cpr4-apss-regulator.c new file mode 100644 index 000000000..114e2acb2 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/target/linux/ipq807x/files-5.15/include/linux/power/qcom/apm.h b/target/linux/ipq807x/files-5.15/include/linux/power/qcom/apm.h new file mode 100644 index 000000000..432683578 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 +#include + +/** + * 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/ipq807x/files-5.15/include/soc/qcom/socinfo.h b/target/linux/ipq807x/files-5.15/include/soc/qcom/socinfo.h new file mode 100644 index 000000000..db4344616 --- /dev/null +++ b/target/linux/ipq807x/files-5.15/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 + +#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/ipq807x/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch b/target/linux/ipq807x/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch new file mode 100644 index 000000000..d9515cef2 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch @@ -0,0 +1,43 @@ +From 63750607afad67e57841689b01a9425822503e0c Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 5 Sep 2021 18:58:16 +0200 +Subject: [PATCH] arm64: dts: qcom: ipq8074: add SPMI bus + +IPQ8074 uses SPMI for communication with the PMIC, so +since its already supported add the DT node for it. + +Signed-off-by: Robert Marko +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20210905165816.655275-1-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -293,6 +293,25 @@ + #reset-cells = <0x1>; + }; + ++ spmi_bus: spmi@200f000 { ++ compatible = "qcom,spmi-pmic-arb"; ++ reg = <0x0200f000 0x001000>, ++ <0x02400000 0x800000>, ++ <0x02c00000 0x800000>, ++ <0x03800000 0x200000>, ++ <0x0200a000 0x000700>; ++ reg-names = "core", "chnls", "obsrvr", "intr", "cnfg"; ++ interrupts = ; ++ interrupt-names = "periph_irq"; ++ qcom,ee = <0>; ++ qcom,channel = <0>; ++ #address-cells = <2>; ++ #size-cells = <0>; ++ interrupt-controller; ++ #interrupt-cells = <4>; ++ cell-index = <0>; ++ }; ++ + sdhc_1: sdhci@7824900 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x7824900 0x500>, <0x7824000 0x800>; diff --git a/target/linux/ipq807x/patches-5.15/0002-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch b/target/linux/ipq807x/patches-5.15/0002-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch new file mode 100644 index 000000000..e6ddc9ed2 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0002-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch @@ -0,0 +1,40 @@ +From 9c0bd8e53774c38bd7859ad4af300a5062430925 Mon Sep 17 00:00:00 2001 +From: Chukun Pan +Date: Fri, 1 Oct 2021 22:54:21 +0800 +Subject: [PATCH] arm64: dts: qcom: ipq8074: Add QUP5 I2C node + +Add node to support the QUP5 I2C controller inside of IPQ8074. +It is exactly the same as QUP2 controllers. +Some routers like ZTE MF269 use this bus. + +Signed-off-by: Chukun Pan +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20211001145421.18302-1-amadeus@jmu.edu.cn +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -430,6 +430,21 @@ + status = "disabled"; + }; + ++ blsp1_i2c5: i2c@78b9000 { ++ compatible = "qcom,i2c-qup-v2.2.1"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ reg = <0x78b9000 0x600>; ++ interrupts = ; ++ clocks = <&gcc GCC_BLSP1_AHB_CLK>, ++ <&gcc GCC_BLSP1_QUP5_I2C_APPS_CLK>; ++ clock-names = "iface", "core"; ++ clock-frequency = <400000>; ++ dmas = <&blsp_dma 21>, <&blsp_dma 20>; ++ dma-names = "rx", "tx"; ++ status = "disabled"; ++ }; ++ + blsp1_i2c6: i2c@78ba000 { + compatible = "qcom,i2c-qup-v2.2.1"; + #address-cells = <1>; diff --git a/target/linux/ipq807x/patches-5.15/0003-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch b/target/linux/ipq807x/patches-5.15/0003-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch new file mode 100644 index 000000000..0bfc54695 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0003-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch @@ -0,0 +1,134 @@ +From 82d61e19fccbf2fe7c018765b3799791916e7f31 Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Wed, 29 Sep 2021 11:42:46 +0800 +Subject: [PATCH] arm64: dts: qcom: msm8996: Move '#clock-cells' to QMP PHY + child node + +'#clock-cells' is a required property of QMP PHY child node, not itself. +Move it to fix the dtbs_check warnings. + +There are only '#clock-cells' removal from SM8350 QMP PHY nodes, because +child nodes already have the property. + +Signed-off-by: Shawn Guo +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20210929034253.24570-4-shawn.guo@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++-- + arch/arm64/boot/dts/qcom/msm8996.dtsi | 4 ++-- + arch/arm64/boot/dts/qcom/msm8998.dtsi | 2 +- + arch/arm64/boot/dts/qcom/sm8350.dtsi | 3 --- + 4 files changed, 5 insertions(+), 8 deletions(-) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -91,7 +91,6 @@ + ssphy_1: phy@58000 { + compatible = "qcom,ipq8074-qmp-usb3-phy"; + reg = <0x00058000 0x1c4>; +- #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -112,6 +111,7 @@ + <0x00058800 0x1f8>, /* PCS */ + <0x00058600 0x044>; /* PCS misc*/ + #phy-cells = <0>; ++ #clock-cells = <1>; + clocks = <&gcc GCC_USB1_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "gcc_usb1_pipe_clk_src"; +@@ -134,7 +134,6 @@ + ssphy_0: phy@78000 { + compatible = "qcom,ipq8074-qmp-usb3-phy"; + reg = <0x00078000 0x1c4>; +- #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -155,6 +154,7 @@ + <0x00078800 0x1f8>, /* PCS */ + <0x00078600 0x044>; /* PCS misc*/ + #phy-cells = <0>; ++ #clock-cells = <1>; + clocks = <&gcc GCC_USB0_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "gcc_usb0_pipe_clk_src"; +--- a/arch/arm64/boot/dts/qcom/msm8996.dtsi ++++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi +@@ -582,7 +582,6 @@ + pcie_phy: phy@34000 { + compatible = "qcom,msm8996-qmp-pcie-phy"; + reg = <0x00034000 0x488>; +- #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -604,6 +603,7 @@ + <0x00035400 0x1dc>; + #phy-cells = <0>; + ++ #clock-cells = <1>; + clock-output-names = "pcie_0_pipe_clk_src"; + clocks = <&gcc GCC_PCIE_0_PIPE_CLK>; + clock-names = "pipe0"; +@@ -2583,7 +2583,6 @@ + usb3phy: phy@7410000 { + compatible = "qcom,msm8996-qmp-usb3-phy"; + reg = <0x07410000 0x1c4>; +- #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -2604,6 +2603,7 @@ + <0x07410600 0x1a8>; + #phy-cells = <0>; + ++ #clock-cells = <1>; + clock-output-names = "usb3_phy_pipe_clk_src"; + clocks = <&gcc GCC_USB3_PHY_PIPE_CLK>; + clock-names = "pipe0"; +--- a/arch/arm64/boot/dts/qcom/msm8998.dtsi ++++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi +@@ -1985,7 +1985,6 @@ + compatible = "qcom,msm8998-qmp-usb3-phy"; + reg = <0x0c010000 0x18c>; + status = "disabled"; +- #clock-cells = <1>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -2006,6 +2005,7 @@ + <0xc010600 0x128>, + <0xc010800 0x200>; + #phy-cells = <0>; ++ #clock-cells = <1>; + clocks = <&gcc GCC_USB3_PHY_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "usb3_phy_pipe_clk_src"; +--- a/arch/arm64/boot/dts/qcom/sm8350.dtsi ++++ b/arch/arm64/boot/dts/qcom/sm8350.dtsi +@@ -1112,7 +1112,6 @@ + reg = <0 0x01d87000 0 0xe10>; + #address-cells = <2>; + #size-cells = <2>; +- #clock-cells = <1>; + ranges; + clock-names = "ref", + "ref_aux"; +@@ -1247,7 +1246,6 @@ + <0 0x088e8000 0 0x20>; + reg-names = "reg-base", "dp_com"; + status = "disabled"; +- #clock-cells = <1>; + #address-cells = <2>; + #size-cells = <2>; + ranges; +@@ -1280,7 +1278,6 @@ + compatible = "qcom,sm8350-qmp-usb3-uni-phy"; + reg = <0 0x088eb000 0 0x200>; + status = "disabled"; +- #clock-cells = <1>; + #address-cells = <2>; + #size-cells = <2>; + ranges; diff --git a/target/linux/ipq807x/patches-5.15/0004-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch b/target/linux/ipq807x/patches-5.15/0004-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch new file mode 100644 index 000000000..c6cc0619f --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0004-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch @@ -0,0 +1,273 @@ +From 1351512f29b4348e6b497f6343896c1033d409b4 Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Wed, 29 Sep 2021 11:42:47 +0800 +Subject: [PATCH] arm64: dts: qcom: Correct QMP PHY child node name + +Many child nodes of QMP PHY are named without following bindings schema +and causing dtbs_check warnings like below. + +phy@1c06000: 'lane@1c06800' does not match any of the regexes: '^phy@[0-9a-f]+$' + arch/arm64/boot/dts/qcom/msm8998-asus-novago-tp370ql.dt.yaml + arch/arm64/boot/dts/qcom/msm8998-hp-envy-x2.dt.yaml + arch/arm64/boot/dts/qcom/msm8998-lenovo-miix-630.dt.yaml + arch/arm64/boot/dts/qcom/msm8998-mtp.dt.yaml + arch/arm64/boot/dts/qcom/msm8998-oneplus-cheeseburger.dt.yaml + arch/arm64/boot/dts/qcom/msm8998-oneplus-dumpling.dt.yaml + +Correct them to fix the warnings. + +Signed-off-by: Shawn Guo +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20210929034253.24570-5-shawn.guo@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq6018.dtsi | 2 +- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++-- + arch/arm64/boot/dts/qcom/msm8996.dtsi | 10 +++++----- + arch/arm64/boot/dts/qcom/msm8998.dtsi | 6 +++--- + arch/arm64/boot/dts/qcom/sdm845.dtsi | 10 +++++----- + arch/arm64/boot/dts/qcom/sm8150.dtsi | 6 +++--- + arch/arm64/boot/dts/qcom/sm8250.dtsi | 10 +++++----- + arch/arm64/boot/dts/qcom/sm8350.dtsi | 2 +- + 8 files changed, 25 insertions(+), 25 deletions(-) + +--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +@@ -401,7 +401,7 @@ + reset-names = "phy", + "common"; + +- pcie_phy0: lane@84200 { ++ pcie_phy0: phy@84200 { + reg = <0x0 0x84200 0x0 0x16c>, /* Serdes Tx */ + <0x0 0x84400 0x0 0x200>, /* Serdes Rx */ + <0x0 0x84800 0x0 0x4f4>; /* PCS: Lane0, COM, PCIE */ +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -105,7 +105,7 @@ + reset-names = "phy","common"; + status = "disabled"; + +- usb1_ssphy: lane@58200 { ++ usb1_ssphy: phy@58200 { + reg = <0x00058200 0x130>, /* Tx */ + <0x00058400 0x200>, /* Rx */ + <0x00058800 0x1f8>, /* PCS */ +@@ -148,7 +148,7 @@ + reset-names = "phy","common"; + status = "disabled"; + +- usb0_ssphy: lane@78200 { ++ usb0_ssphy: phy@78200 { + reg = <0x00078200 0x130>, /* Tx */ + <0x00078400 0x200>, /* Rx */ + <0x00078800 0x1f8>, /* PCS */ +--- a/arch/arm64/boot/dts/qcom/msm8996.dtsi ++++ b/arch/arm64/boot/dts/qcom/msm8996.dtsi +@@ -597,7 +597,7 @@ + reset-names = "phy", "common", "cfg"; + status = "disabled"; + +- pciephy_0: lane@35000 { ++ pciephy_0: phy@35000 { + reg = <0x00035000 0x130>, + <0x00035200 0x200>, + <0x00035400 0x1dc>; +@@ -611,7 +611,7 @@ + reset-names = "lane0"; + }; + +- pciephy_1: lane@36000 { ++ pciephy_1: phy@36000 { + reg = <0x00036000 0x130>, + <0x00036200 0x200>, + <0x00036400 0x1dc>; +@@ -624,7 +624,7 @@ + reset-names = "lane1"; + }; + +- pciephy_2: lane@37000 { ++ pciephy_2: phy@37000 { + reg = <0x00037000 0x130>, + <0x00037200 0x200>, + <0x00037400 0x1dc>; +@@ -1743,7 +1743,7 @@ + reset-names = "ufsphy"; + status = "disabled"; + +- ufsphy_lane: lanes@627400 { ++ ufsphy_lane: phy@627400 { + reg = <0x627400 0x12c>, + <0x627600 0x200>, + <0x627c00 0x1b4>; +@@ -2597,7 +2597,7 @@ + reset-names = "phy", "common"; + status = "disabled"; + +- ssusb_phy_0: lane@7410200 { ++ ssusb_phy_0: phy@7410200 { + reg = <0x07410200 0x200>, + <0x07410400 0x130>, + <0x07410600 0x1a8>; +--- a/arch/arm64/boot/dts/qcom/msm8998.dtsi ++++ b/arch/arm64/boot/dts/qcom/msm8998.dtsi +@@ -994,7 +994,7 @@ + vdda-phy-supply = <&vreg_l1a_0p875>; + vdda-pll-supply = <&vreg_l2a_1p2>; + +- pciephy: lane@1c06800 { ++ pciephy: phy@1c06800 { + reg = <0x01c06200 0x128>, <0x01c06400 0x1fc>, <0x01c06800 0x20c>; + #phy-cells = <0>; + +@@ -1066,7 +1066,7 @@ + reset-names = "ufsphy"; + resets = <&ufshc 0>; + +- ufsphy_lanes: lanes@1da7400 { ++ ufsphy_lanes: phy@1da7400 { + reg = <0x01da7400 0x128>, + <0x01da7600 0x1fc>, + <0x01da7c00 0x1dc>, +@@ -1998,7 +1998,7 @@ + <&gcc GCC_USB3PHY_PHY_BCR>; + reset-names = "phy", "common"; + +- usb1_ssphy: lane@c010200 { ++ usb1_ssphy: phy@c010200 { + reg = <0xc010200 0x128>, + <0xc010400 0x200>, + <0xc010c00 0x20c>, +--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi ++++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi +@@ -2064,7 +2064,7 @@ + + status = "disabled"; + +- pcie0_lane: lanes@1c06200 { ++ pcie0_lane: phy@1c06200 { + reg = <0 0x01c06200 0 0x128>, + <0 0x01c06400 0 0x1fc>, + <0 0x01c06800 0 0x218>, +@@ -2174,7 +2174,7 @@ + + status = "disabled"; + +- pcie1_lane: lanes@1c06200 { ++ pcie1_lane: phy@1c06200 { + reg = <0 0x01c0a800 0 0x800>, + <0 0x01c0a800 0 0x800>, + <0 0x01c0b800 0 0x400>; +@@ -2302,7 +2302,7 @@ + reset-names = "ufsphy"; + status = "disabled"; + +- ufs_mem_phy_lanes: lanes@1d87400 { ++ ufs_mem_phy_lanes: phy@1d87400 { + reg = <0 0x01d87400 0 0x108>, + <0 0x01d87600 0 0x1e0>, + <0 0x01d87c00 0 0x1dc>, +@@ -3699,7 +3699,7 @@ + <&gcc GCC_USB3_PHY_PRIM_BCR>; + reset-names = "phy", "common"; + +- usb_1_ssphy: lanes@88e9200 { ++ usb_1_ssphy: phy@88e9200 { + reg = <0 0x088e9200 0 0x128>, + <0 0x088e9400 0 0x200>, + <0 0x088e9c00 0 0x218>, +@@ -3732,7 +3732,7 @@ + <&gcc GCC_USB3_PHY_SEC_BCR>; + reset-names = "phy", "common"; + +- usb_2_ssphy: lane@88eb200 { ++ usb_2_ssphy: phy@88eb200 { + reg = <0 0x088eb200 0 0x128>, + <0 0x088eb400 0 0x1fc>, + <0 0x088eb800 0 0x218>, +--- a/arch/arm64/boot/dts/qcom/sm8150.dtsi ++++ b/arch/arm64/boot/dts/qcom/sm8150.dtsi +@@ -1692,7 +1692,7 @@ + reset-names = "ufsphy"; + status = "disabled"; + +- ufs_mem_phy_lanes: lanes@1d87400 { ++ ufs_mem_phy_lanes: phy@1d87400 { + reg = <0 0x01d87400 0 0x108>, + <0 0x01d87600 0 0x1e0>, + <0 0x01d87c00 0 0x1dc>, +@@ -3010,7 +3010,7 @@ + <&gcc GCC_USB3_PHY_PRIM_BCR>; + reset-names = "phy", "common"; + +- usb_1_ssphy: lanes@88e9200 { ++ usb_1_ssphy: phy@88e9200 { + reg = <0 0x088e9200 0 0x200>, + <0 0x088e9400 0 0x200>, + <0 0x088e9c00 0 0x218>, +@@ -3043,7 +3043,7 @@ + <&gcc GCC_USB3_PHY_SEC_BCR>; + reset-names = "phy", "common"; + +- usb_2_ssphy: lane@88eb200 { ++ usb_2_ssphy: phy@88eb200 { + reg = <0 0x088eb200 0 0x200>, + <0 0x088eb400 0 0x200>, + <0 0x088eb800 0 0x800>, +--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi ++++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi +@@ -1463,7 +1463,7 @@ + + status = "disabled"; + +- pcie0_lane: lanes@1c06200 { ++ pcie0_lane: phy@1c06200 { + reg = <0 0x1c06200 0 0x170>, /* tx */ + <0 0x1c06400 0 0x200>, /* rx */ + <0 0x1c06800 0 0x1f0>, /* pcs */ +@@ -1567,7 +1567,7 @@ + + status = "disabled"; + +- pcie1_lane: lanes@1c0e200 { ++ pcie1_lane: phy@1c0e200 { + reg = <0 0x1c0e200 0 0x170>, /* tx0 */ + <0 0x1c0e400 0 0x200>, /* rx0 */ + <0 0x1c0ea00 0 0x1f0>, /* pcs */ +@@ -1673,7 +1673,7 @@ + + status = "disabled"; + +- pcie2_lane: lanes@1c16200 { ++ pcie2_lane: phy@1c16200 { + reg = <0 0x1c16200 0 0x170>, /* tx0 */ + <0 0x1c16400 0 0x200>, /* rx0 */ + <0 0x1c16a00 0 0x1f0>, /* pcs */ +@@ -1750,7 +1750,7 @@ + reset-names = "ufsphy"; + status = "disabled"; + +- ufs_mem_phy_lanes: lanes@1d87400 { ++ ufs_mem_phy_lanes: phy@1d87400 { + reg = <0 0x01d87400 0 0x108>, + <0 0x01d87600 0 0x1e0>, + <0 0x01d87c00 0 0x1dc>, +@@ -2330,7 +2330,7 @@ + <&gcc GCC_USB3_PHY_SEC_BCR>; + reset-names = "phy", "common"; + +- usb_2_ssphy: lanes@88eb200 { ++ usb_2_ssphy: phy@88eb200 { + reg = <0 0x088eb200 0 0x200>, + <0 0x088eb400 0 0x200>, + <0 0x088eb800 0 0x800>; +--- a/arch/arm64/boot/dts/qcom/sm8350.dtsi ++++ b/arch/arm64/boot/dts/qcom/sm8350.dtsi +@@ -1122,7 +1122,7 @@ + reset-names = "ufsphy"; + status = "disabled"; + +- ufs_mem_phy_lanes: lanes@1d87400 { ++ ufs_mem_phy_lanes: phy@1d87400 { + reg = <0 0x01d87400 0 0x108>, + <0 0x01d87600 0 0x1e0>, + <0 0x01d87c00 0 0x1dc>, diff --git a/target/linux/ipq807x/patches-5.15/0005-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch b/target/linux/ipq807x/patches-5.15/0005-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch new file mode 100644 index 000000000..b3dce7dd4 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0005-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch @@ -0,0 +1,94 @@ +From 942bcd33ed455ad40b71a59901bd926bbf4a500e Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Wed, 29 Sep 2021 11:42:51 +0800 +Subject: [PATCH] arm64: dts: qcom: Fix IPQ8074 PCIe PHY nodes + +IPQ8074 PCIe PHY nodes are broken in the many ways: + +- '#address-cells', '#size-cells' and 'ranges' are missing. +- Child phy/lane node is missing, and the child properties like + '#phy-cells' and 'clocks' are mistakenly put into parent node. +- The clocks properties for parent node are missing. + +Fix them to get the nodes comply with the bindings schema. + +Signed-off-by: Shawn Guo +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20210929034253.24570-9-shawn.guo@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 46 +++++++++++++++++++++------ + 1 file changed, 36 insertions(+), 10 deletions(-) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -174,34 +174,60 @@ + status = "disabled"; + }; + +- pcie_phy0: phy@86000 { ++ pcie_qmp0: phy@86000 { + compatible = "qcom,ipq8074-qmp-pcie-phy"; + reg = <0x00086000 0x1000>; +- #phy-cells = <0>; +- clocks = <&gcc GCC_PCIE0_PIPE_CLK>; +- clock-names = "pipe_clk"; +- clock-output-names = "pcie20_phy0_pipe_clk"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ 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"; + status = "disabled"; ++ ++ pcie_phy0: phy@86200 { ++ reg = <0x86200 0x16c>, ++ <0x86400 0x200>, ++ <0x86800 0x4f4>; ++ #phy-cells = <0>; ++ #clock-cells = <0>; ++ clocks = <&gcc GCC_PCIE0_PIPE_CLK>; ++ clock-names = "pipe0"; ++ clock-output-names = "pcie_0_pipe_clk"; ++ }; + }; + +- pcie_phy1: phy@8e000 { ++ pcie_qmp1: phy@8e000 { + compatible = "qcom,ipq8074-qmp-pcie-phy"; + reg = <0x0008e000 0x1000>; +- #phy-cells = <0>; +- clocks = <&gcc GCC_PCIE1_PIPE_CLK>; +- clock-names = "pipe_clk"; +- clock-output-names = "pcie20_phy1_pipe_clk"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; + ++ clocks = <&gcc GCC_PCIE1_AUX_CLK>, ++ <&gcc GCC_PCIE1_AHB_CLK>; ++ clock-names = "aux", "cfg_ahb"; + resets = <&gcc GCC_PCIE1_PHY_BCR>, + <&gcc GCC_PCIE1PHY_PHY_BCR>; + reset-names = "phy", + "common"; + status = "disabled"; ++ ++ pcie_phy1: phy@8e200 { ++ reg = <0x8e200 0x16c>, ++ <0x8e400 0x200>, ++ <0x8e800 0x4f4>; ++ #phy-cells = <0>; ++ #clock-cells = <0>; ++ clocks = <&gcc GCC_PCIE1_PIPE_CLK>; ++ clock-names = "pipe0"; ++ clock-output-names = "pcie_1_pipe_clk"; ++ }; + }; + + prng: rng@e3000 { diff --git a/target/linux/ipq807x/patches-5.15/0006-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch b/target/linux/ipq807x/patches-5.15/0006-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch new file mode 100644 index 000000000..aeeafbb62 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0006-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch @@ -0,0 +1,36 @@ +From d201f67714a302b12ad3d78b982963342939629c Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 7 Oct 2021 13:58:46 +0200 +Subject: [PATCH] arm64: dts: qcom: ipq8074: add MDIO bus + +IPQ8074 uses an IPQ4019 compatible MDIO controller that is already +supported in the kernel, so add the DT node in order to use it. + +Signed-off-by: Robert Marko +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20211007115846.26255-1-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -230,6 +230,18 @@ + }; + }; + ++ mdio: mdio@90000 { ++ compatible = "qcom,ipq4019-mdio"; ++ reg = <0x00090000 0x64>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ clocks = <&gcc GCC_MDIO_AHB_CLK>; ++ clock-names = "gcc_mdio_ahb_clk"; ++ ++ status = "disabled"; ++ }; ++ + prng: rng@e3000 { + compatible = "qcom,prng-ee"; + reg = <0x000e3000 0x1000>; diff --git a/target/linux/ipq807x/patches-5.15/0007-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch b/target/linux/ipq807x/patches-5.15/0007-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch new file mode 100644 index 000000000..a344a4b27 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0007-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch @@ -0,0 +1,50 @@ +From adfde5bbb4afd5e47371b74a0a4c9ec02fcc8d58 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 5 Sep 2021 19:11:31 +0200 +Subject: [PATCH] soc: qcom: socinfo: Add IPQ8074 family ID-s + +IPQ8074 family SoC ID-s are missing, so lets add them based on +the downstream driver. + +Signed-off-by: Robert Marko +Reviewed-by: Kathiravan T +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20210905171131.660885-1-robimarko@gmail.com +--- + drivers/soc/qcom/socinfo.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +--- a/drivers/soc/qcom/socinfo.c ++++ b/drivers/soc/qcom/socinfo.c +@@ -281,19 +281,31 @@ static const struct soc_id soc_id[] = { + { 319, "APQ8098" }, + { 321, "SDM845" }, + { 322, "MDM9206" }, ++ { 323, "IPQ8074" }, + { 324, "SDA660" }, + { 325, "SDM658" }, + { 326, "SDA658" }, + { 327, "SDA630" }, + { 338, "SDM450" }, + { 341, "SDA845" }, ++ { 342, "IPQ8072" }, ++ { 343, "IPQ8076" }, ++ { 344, "IPQ8078" }, + { 345, "SDM636" }, + { 346, "SDA636" }, + { 349, "SDM632" }, + { 350, "SDA632" }, + { 351, "SDA450" }, + { 356, "SM8250" }, ++ { 375, "IPQ8070" }, ++ { 376, "IPQ8071" }, ++ { 389, "IPQ8072A" }, ++ { 390, "IPQ8074A" }, ++ { 391, "IPQ8076A" }, ++ { 392, "IPQ8078A" }, + { 394, "SM6125" }, ++ { 395, "IPQ8070A" }, ++ { 396, "IPQ8071A" }, + { 402, "IPQ6018" }, + { 403, "IPQ6028" }, + { 421, "IPQ6000" }, diff --git a/target/linux/ipq807x/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch b/target/linux/ipq807x/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch new file mode 100644 index 000000000..c1cbc77cc --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch @@ -0,0 +1,166 @@ +From 36e21b500f0432e746cfdb3e14eb22761222511c Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Thu, 30 Sep 2021 11:21:10 -0700 +Subject: [PATCH] soc: qcom: smem: Support reserved-memory description + +Practically all modern Qualcomm platforms has a single reserved-memory +region for SMEM. So rather than having to describe SMEM in the form of a +node with a reference to a reserved-memory node, allow the SMEM device +to be instantiated directly from the reserved-memory node. + +The current means of falling back to dereferencing the "memory-region" +is kept as a fallback, if it's determined that the SMEM node is a +reserved-memory node. + +The "qcom,smem" compatible is added to the reserved_mem_matches list, to +allow the reserved-memory device to be probed. + +In order to retain the readability of the code, the resolution of +resources is split from the actual ioremapping. + +Signed-off-by: Bjorn Andersson +Acked-by: Rob Herring +Reviewed-by: Vladimir Zapolskiy +Link: https://lore.kernel.org/r/20210930182111.57353-4-bjorn.andersson@linaro.org +--- + drivers/of/platform.c | 1 + + drivers/soc/qcom/smem.c | 57 ++++++++++++++++++++++++++++------------- + 2 files changed, 40 insertions(+), 18 deletions(-) + +--- a/drivers/of/platform.c ++++ b/drivers/of/platform.c +@@ -509,6 +509,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_po + static const struct of_device_id reserved_mem_matches[] = { + { .compatible = "qcom,rmtfs-mem" }, + { .compatible = "qcom,cmd-db" }, ++ { .compatible = "qcom,smem" }, + { .compatible = "ramoops" }, + { .compatible = "nvmem-rmem" }, + {} +--- a/drivers/soc/qcom/smem.c ++++ b/drivers/soc/qcom/smem.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -240,7 +241,7 @@ static const u8 SMEM_INFO_MAGIC[] = { 0x + * @size: size of the memory region + */ + struct smem_region { +- u32 aux_base; ++ phys_addr_t aux_base; + void __iomem *virt_base; + size_t size; + }; +@@ -499,7 +500,7 @@ static void *qcom_smem_get_global(struct + for (i = 0; i < smem->num_regions; i++) { + region = &smem->regions[i]; + +- if (region->aux_base == aux_base || !aux_base) { ++ if ((u32)region->aux_base == aux_base || !aux_base) { + if (size != NULL) + *size = le32_to_cpu(entry->size); + return region->virt_base + le32_to_cpu(entry->offset); +@@ -664,7 +665,7 @@ phys_addr_t qcom_smem_virt_to_phys(void + if (p < region->virt_base + region->size) { + u64 offset = p - region->virt_base; + +- return (phys_addr_t)region->aux_base + offset; ++ return region->aux_base + offset; + } + } + +@@ -863,12 +864,12 @@ qcom_smem_enumerate_partitions(struct qc + return 0; + } + +-static int qcom_smem_map_memory(struct qcom_smem *smem, struct device *dev, +- const char *name, int i) ++static int qcom_smem_resolve_mem(struct qcom_smem *smem, const char *name, ++ struct smem_region *region) + { ++ struct device *dev = smem->dev; + struct device_node *np; + struct resource r; +- resource_size_t size; + int ret; + + np = of_parse_phandle(dev->of_node, name, 0); +@@ -881,13 +882,9 @@ static int qcom_smem_map_memory(struct q + of_node_put(np); + if (ret) + return ret; +- size = resource_size(&r); + +- smem->regions[i].virt_base = devm_ioremap_wc(dev, r.start, size); +- if (!smem->regions[i].virt_base) +- return -ENOMEM; +- smem->regions[i].aux_base = (u32)r.start; +- smem->regions[i].size = size; ++ region->aux_base = r.start; ++ region->size = resource_size(&r); + + return 0; + } +@@ -895,12 +892,14 @@ static int qcom_smem_map_memory(struct q + static int qcom_smem_probe(struct platform_device *pdev) + { + struct smem_header *header; ++ struct reserved_mem *rmem; + struct qcom_smem *smem; + size_t array_size; + int num_regions; + int hwlock_id; + u32 version; + int ret; ++ int i; + + num_regions = 1; + if (of_find_property(pdev->dev.of_node, "qcom,rpm-msg-ram", NULL)) +@@ -914,13 +913,35 @@ static int qcom_smem_probe(struct platfo + smem->dev = &pdev->dev; + smem->num_regions = num_regions; + +- ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); +- if (ret) +- return ret; +- +- if (num_regions > 1 && (ret = qcom_smem_map_memory(smem, &pdev->dev, +- "qcom,rpm-msg-ram", 1))) +- return ret; ++ rmem = of_reserved_mem_lookup(pdev->dev.of_node); ++ if (rmem) { ++ smem->regions[0].aux_base = rmem->base; ++ smem->regions[0].size = rmem->size; ++ } else { ++ /* ++ * Fall back to the memory-region reference, if we're not a ++ * reserved-memory node. ++ */ ++ ret = qcom_smem_resolve_mem(smem, "memory-region", &smem->regions[0]); ++ if (ret) ++ return ret; ++ } ++ ++ if (num_regions > 1) { ++ ret = qcom_smem_resolve_mem(smem, "qcom,rpm-msg-ram", &smem->regions[1]); ++ if (ret) ++ return ret; ++ } ++ ++ for (i = 0; i < num_regions; i++) { ++ smem->regions[i].virt_base = devm_ioremap_wc(&pdev->dev, ++ smem->regions[i].aux_base, ++ smem->regions[i].size); ++ if (!smem->regions[i].virt_base) { ++ dev_err(&pdev->dev, "failed to remap %pa\n", &smem->regions[i].aux_base); ++ return -ENOMEM; ++ } ++ } + + header = smem->regions[0].virt_base; + if (le32_to_cpu(header->initialized) != 1 || diff --git a/target/linux/ipq807x/patches-5.15/0010-v5.18-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch b/target/linux/ipq807x/patches-5.15/0010-v5.18-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch new file mode 100644 index 000000000..1197856c4 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0010-v5.18-mtd-parsers-qcom-Don-t-print-error-message-on-EPROBE.patch @@ -0,0 +1,32 @@ +From 030e9812dc18de9ce9f1143cc6c84918608befca Mon Sep 17 00:00:00 2001 +From: Bryan O'Donoghue +Date: Mon, 3 Jan 2022 03:03:16 +0000 +Subject: [PATCH 2/2] mtd: parsers: qcom: Don't print error message on + -EPROBE_DEFER + +Its possible for the main smem driver to not be loaded by the time we come +along to parse the smem partition description but, this is a perfectly +normal thing. + +No need to print out an error message in this case. + +Signed-off-by: Bryan O'Donoghue +Reviewed-by: Manivannan Sadhasivam +Signed-off-by: Miquel Raynal +Link: https://lore.kernel.org/linux-mtd/20220103030316.58301-3-bryan.odonoghue@linaro.org +--- + drivers/mtd/parsers/qcomsmempart.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +--- a/drivers/mtd/parsers/qcomsmempart.c ++++ b/drivers/mtd/parsers/qcomsmempart.c +@@ -75,7 +75,8 @@ static int parse_qcomsmem_part(struct mt + pr_debug("Parsing partition table info from SMEM\n"); + ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len); + if (IS_ERR(ptable)) { +- pr_err("Error reading partition table header\n"); ++ if (PTR_ERR(ptable) != -EPROBE_DEFER) ++ pr_err("Error reading partition table header\n"); + return PTR_ERR(ptable); + } + diff --git a/target/linux/ipq807x/patches-5.15/0011-v5.18-arm64-dts-ipq8074-add-SMEM-support.patch b/target/linux/ipq807x/patches-5.15/0011-v5.18-arm64-dts-ipq8074-add-SMEM-support.patch new file mode 100644 index 000000000..dd7ff3e5a --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0011-v5.18-arm64-dts-ipq8074-add-SMEM-support.patch @@ -0,0 +1,49 @@ +From f63c96c02671c00871d180fb5b436e53f4847d3c Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 6 Jan 2022 22:25:12 +0100 +Subject: [PATCH] arm64: dts: ipq8074: add SMEM support + +IPQ8074 uses SMEM like other modern QCA SoC-s, so since its already +supported by the kernel add the required DT nodes. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -76,6 +76,20 @@ + method = "smc"; + }; + ++ reserved-memory { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ ranges; ++ ++ smem@4ab00000 { ++ compatible = "qcom,smem"; ++ reg = <0x0 0x4ab00000 0x0 0x00100000>; ++ no-map; ++ ++ hwlocks = <&tcsr_mutex 0>; ++ }; ++ }; ++ + firmware { + scm { + compatible = "qcom,scm-ipq8074", "qcom,scm"; +@@ -331,6 +345,12 @@ + #reset-cells = <0x1>; + }; + ++ tcsr_mutex: hwlock@1905000 { ++ compatible = "qcom,tcsr-mutex"; ++ reg = <0x01905000 0x20000>; ++ #hwlock-cells = <1>; ++ }; ++ + spmi_bus: spmi@200f000 { + compatible = "qcom,spmi-pmic-arb"; + reg = <0x0200f000 0x001000>, diff --git a/target/linux/ipq807x/patches-5.15/0101-arm64-dts-ipq8074-add-reserved-memory-nodes.patch b/target/linux/ipq807x/patches-5.15/0101-arm64-dts-ipq8074-add-reserved-memory-nodes.patch new file mode 100644 index 000000000..cba302095 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0101-arm64-dts-ipq8074-add-reserved-memory-nodes.patch @@ -0,0 +1,75 @@ +From 4b6d5caa1747bbe0eca15d4d20f028748c544cd0 Mon Sep 17 00:00:00 2001 +From: Robert Marko +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 +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 40 +++++++++++++++++++++++++++ + 1 file changed, 40 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -81,6 +81,31 @@ + #size-cells = <2>; + ranges; + ++ nss@40000000 { ++ no-map; ++ reg = <0x0 0x40000000 0x0 0x01000000>; ++ }; ++ ++ tzapp_region: tzapp@4a400000 { ++ no-map; ++ reg = <0x0 0x4a400000 0x0 0x00200000>; ++ }; ++ ++ uboot@4a600000 { ++ no-map; ++ reg = <0x0 0x4a600000 0x0 0x00400000>; ++ }; ++ ++ sbl@4aa00000 { ++ no-map; ++ reg = <0x0 0x4aa00000 0x0 0x00100000>; ++ }; ++ ++ tz@4ac00000 { ++ no-map; ++ reg = <0x0 0x4ac00000 0x0 0x00400000>; ++ }; ++ + smem@4ab00000 { + compatible = "qcom,smem"; + reg = <0x0 0x4ab00000 0x0 0x00100000>; +@@ -88,6 +113,21 @@ + + hwlocks = <&tcsr_mutex 0>; + }; ++ ++ 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/ipq807x/patches-5.15/0103-phy-qcom-qmp-Add-IPQ8074-PCIe-Gen3-QMP-PHY-support.patch b/target/linux/ipq807x/patches-5.15/0103-phy-qcom-qmp-Add-IPQ8074-PCIe-Gen3-QMP-PHY-support.patch new file mode 100644 index 000000000..62a36baa3 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0103-phy-qcom-qmp-Add-IPQ8074-PCIe-Gen3-QMP-PHY-support.patch @@ -0,0 +1,369 @@ +From 6fe752e3927ee9d9cad6ad197d5fe58c23a61935 Mon Sep 17 00:00:00 2001 +From: Sivaprakash Murugesan +Date: Wed, 29 Jul 2020 21:00:04 +0530 +Subject: [PATCH] phy: qcom-qmp: Add IPQ8074 PCIe Gen3 QMP PHY support + +IPQ8074 has two PCIe ports, One Gen2 and one Gen3 port. +Since support for Gen2 PHY is already available, add support for +PCIe Gen3 PHY. + +Co-developed-by: Selvam Sathappan Periakaruppan +Signed-off-by: Selvam Sathappan Periakaruppan +Signed-off-by: Sivaprakash Murugesan +Signed-off-by: Robert Marko +--- + drivers/phy/qualcomm/phy-qcom-pcie3-qmp.h | 139 ++++++++++++++++++ + drivers/phy/qualcomm/phy-qcom-qmp.c | 171 +++++++++++++++++++++- + 2 files changed, 308 insertions(+), 2 deletions(-) + create mode 100644 drivers/phy/qualcomm/phy-qcom-pcie3-qmp.h + +--- /dev/null ++++ b/drivers/phy/qualcomm/phy-qcom-pcie3-qmp.h +@@ -0,0 +1,139 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++ ++/* ++ * Copyright (c) 2020, The Linux Foundation. All rights reserved. ++ */ ++ ++#ifndef PHY_QCOM_PCIE_H ++#define PHY_QCOM_PCIE_H ++ ++/* QMP V2 PCIE PHY - Found in IPQ8074 gen3 port - QSERDES PLL registers */ ++#define QSERDES_PLL_BG_TIMER 0x00c ++#define QSERDES_PLL_SSC_PER1 0x01c ++#define QSERDES_PLL_SSC_PER2 0x020 ++#define QSERDES_PLL_SSC_STEP_SIZE1_MODE0 0x024 ++#define QSERDES_PLL_SSC_STEP_SIZE2_MODE0 0x028 ++#define QSERDES_PLL_SSC_STEP_SIZE1_MODE1 0x02c ++#define QSERDES_PLL_SSC_STEP_SIZE2_MODE1 0x030 ++#define QSERDES_PLL_BIAS_EN_CLKBUFLR_EN 0x03c ++#define QSERDES_PLL_CLK_ENABLE1 0x040 ++#define QSERDES_PLL_SYS_CLK_CTRL 0x044 ++#define QSERDES_PLL_SYSCLK_BUF_ENABLE 0x048 ++#define QSERDES_PLL_PLL_IVCO 0x050 ++#define QSERDES_PLL_LOCK_CMP1_MODE0 0x054 ++#define QSERDES_PLL_LOCK_CMP2_MODE0 0x058 ++#define QSERDES_PLL_LOCK_CMP1_MODE1 0x060 ++#define QSERDES_PLL_LOCK_CMP2_MODE1 0x064 ++#define QSERDES_PLL_BG_TRIM 0x074 ++#define QSERDES_PLL_CLK_EP_DIV_MODE0 0x078 ++#define QSERDES_PLL_CLK_EP_DIV_MODE1 0x07c ++#define QSERDES_PLL_CP_CTRL_MODE0 0x080 ++#define QSERDES_PLL_CP_CTRL_MODE1 0x084 ++#define QSERDES_PLL_PLL_RCTRL_MODE0 0x088 ++#define QSERDES_PLL_PLL_RCTRL_MODE1 0x08C ++#define QSERDES_PLL_PLL_CCTRL_MODE0 0x090 ++#define QSERDES_PLL_PLL_CCTRL_MODE1 0x094 ++#define QSERDES_PLL_BIAS_EN_CTRL_BY_PSM 0x0a4 ++#define QSERDES_PLL_SYSCLK_EN_SEL 0x0a8 ++#define QSERDES_PLL_RESETSM_CNTRL 0x0b0 ++#define QSERDES_PLL_LOCK_CMP_EN 0x0c4 ++#define QSERDES_PLL_DEC_START_MODE0 0x0cc ++#define QSERDES_PLL_DEC_START_MODE1 0x0d0 ++#define QSERDES_PLL_DIV_FRAC_START1_MODE0 0x0d8 ++#define QSERDES_PLL_DIV_FRAC_START2_MODE0 0x0dc ++#define QSERDES_PLL_DIV_FRAC_START3_MODE0 0x0e0 ++#define QSERDES_PLL_DIV_FRAC_START1_MODE1 0x0e4 ++#define QSERDES_PLL_DIV_FRAC_START2_MODE1 0x0e8 ++#define QSERDES_PLL_DIV_FRAC_START3_MODE1 0x0eC ++#define QSERDES_PLL_INTEGLOOP_GAIN0_MODE0 0x100 ++#define QSERDES_PLL_INTEGLOOP_GAIN1_MODE0 0x104 ++#define QSERDES_PLL_INTEGLOOP_GAIN0_MODE1 0x108 ++#define QSERDES_PLL_INTEGLOOP_GAIN1_MODE1 0x10c ++#define QSERDES_PLL_VCO_TUNE_MAP 0x120 ++#define QSERDES_PLL_VCO_TUNE1_MODE0 0x124 ++#define QSERDES_PLL_VCO_TUNE2_MODE0 0x128 ++#define QSERDES_PLL_VCO_TUNE1_MODE1 0x12c ++#define QSERDES_PLL_VCO_TUNE2_MODE1 0x130 ++#define QSERDES_PLL_VCO_TUNE_TIMER1 0x13c ++#define QSERDES_PLL_VCO_TUNE_TIMER2 0x140 ++#define QSERDES_PLL_CLK_SELECT 0x16c ++#define QSERDES_PLL_HSCLK_SEL 0x170 ++#define QSERDES_PLL_CORECLK_DIV 0x17c ++#define QSERDES_PLL_CORE_CLK_EN 0x184 ++#define QSERDES_PLL_CMN_CONFIG 0x18c ++#define QSERDES_PLL_SVS_MODE_CLK_SEL 0x194 ++#define QSERDES_PLL_CORECLK_DIV_MODE1 0x1b4 ++ ++/* QMP V2 PCIE PHY - Found in IPQ8074 gen3 port - - QSERDES TX registers */ ++#define QSERDES_TX0_RES_CODE_LANE_OFFSET_TX 0x03c ++#define QSERDES_TX0_HIGHZ_DRVR_EN 0x058 ++#define QSERDES_TX0_LANE_MODE_1 0x084 ++#define QSERDES_TX0_RCV_DETECT_LVL_2 0x09c ++ ++/* QMP V2 PCIE PHY - Found in IPQ8074 gen3 port - QSERDES RX registers */ ++#define QSERDES_RX0_UCDR_FO_GAIN 0x008 ++#define QSERDES_RX0_UCDR_SO_GAIN 0x014 ++#define QSERDES_RX0_UCDR_SO_SATURATION_AND_ENABLE 0x034 ++#define QSERDES_RX0_UCDR_PI_CONTROLS 0x044 ++#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL2 0x0ec ++#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL3 0x0f0 ++#define QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL4 0x0f4 ++#define QSERDES_RX0_RX_IDAC_TSETTLE_LOW 0x0f8 ++#define QSERDES_RX0_RX_IDAC_TSETTLE_HIGH 0x0fc ++#define QSERDES_RX0_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x110 ++#define QSERDES_RX0_RX_OFFSET_ADAPTOR_CNTRL2 0x114 ++#define QSERDES_RX0_SIGDET_ENABLES 0x118 ++#define QSERDES_RX0_SIGDET_CNTRL 0x11c ++#define QSERDES_RX0_SIGDET_DEGLITCH_CNTRL 0x124 ++#define QSERDES_RX0_RX_MODE_00_LOW 0x170 ++#define QSERDES_RX0_RX_MODE_00_HIGH 0x174 ++#define QSERDES_RX0_RX_MODE_00_HIGH2 0x178 ++#define QSERDES_RX0_RX_MODE_00_HIGH3 0x17c ++#define QSERDES_RX0_RX_MODE_00_HIGH4 0x180 ++#define QSERDES_RX0_RX_MODE_01_LOW 0x184 ++#define QSERDES_RX0_RX_MODE_01_HIGH 0x188 ++#define QSERDES_RX0_RX_MODE_01_HIGH2 0x18c ++#define QSERDES_RX0_RX_MODE_01_HIGH3 0x190 ++#define QSERDES_RX0_RX_MODE_01_HIGH4 0x194 ++#define QSERDES_RX0_RX_MODE_10_LOW 0x198 ++#define QSERDES_RX0_RX_MODE_10_HIGH 0x19c ++#define QSERDES_RX0_RX_MODE_10_HIGH2 0x1a0 ++#define QSERDES_RX0_RX_MODE_10_HIGH3 0x1a4 ++#define QSERDES_RX0_RX_MODE_10_HIGH4 0x1a8 ++#define QSERDES_RX0_DFE_EN_TIMER 0x1b4 ++ ++/* QMP V2 PCIE PHY - Found in IPQ8074 gen3 port - PCS registers */ ++ ++#define PCS_COM_FLL_CNTRL1 0x098 ++#define PCS_COM_FLL_CNTRL2 0x09c ++#define PCS_COM_FLL_CNT_VAL_L 0x0a0 ++#define PCS_COM_FLL_CNT_VAL_H_TOL 0x0a4 ++#define PCS_COM_FLL_MAN_CODE 0x0a8 ++#define PCS_COM_REFGEN_REQ_CONFIG1 0x0dc ++#define PCS_COM_G12S1_TXDEEMPH_M3P5DB 0x16c ++#define PCS_COM_RX_SIGDET_LVL 0x188 ++#define PCS_COM_P2U3_WAKEUP_DLY_TIME_AUXCLK_L 0x1a4 ++#define PCS_COM_P2U3_WAKEUP_DLY_TIME_AUXCLK_H 0x1a8 ++#define PCS_COM_RX_DCC_CAL_CONFIG 0x1d8 ++#define PCS_COM_EQ_CONFIG5 0x1ec ++ ++/* QMP V2 PCIE PHY - Found in IPQ8074 gen3 port - PCS Misc registers */ ++ ++#define PCS_PCIE_POWER_STATE_CONFIG2 0x40c ++#define PCS_PCIE_POWER_STATE_CONFIG4 0x414 ++#define PCS_PCIE_ENDPOINT_REFCLK_DRIVE 0x41c ++#define PCS_PCIE_L1P1_WAKEUP_DLY_TIME_AUXCLK_L 0x440 ++#define PCS_PCIE_L1P1_WAKEUP_DLY_TIME_AUXCLK_H 0x444 ++#define PCS_PCIE_L1P2_WAKEUP_DLY_TIME_AUXCLK_L 0x448 ++#define PCS_PCIE_L1P2_WAKEUP_DLY_TIME_AUXCLK_H 0x44c ++#define PCS_PCIE_OSC_DTCT_CONFIG2 0x45c ++#define PCS_PCIE_OSC_DTCT_MODE2_CONFIG2 0x478 ++#define PCS_PCIE_OSC_DTCT_MODE2_CONFIG4 0x480 ++#define PCS_PCIE_OSC_DTCT_MODE2_CONFIG5 0x484 ++#define PCS_PCIE_OSC_DTCT_ACTIONS 0x490 ++#define PCS_PCIE_EQ_CONFIG1 0x4a0 ++#define PCS_PCIE_EQ_CONFIG2 0x4a4 ++#define PCS_PCIE_PRESET_P10_PRE 0x4bc ++#define PCS_PCIE_PRESET_P10_POST 0x4e0 ++ ++#endif +--- a/drivers/phy/qualcomm/phy-qcom-qmp.c ++++ b/drivers/phy/qualcomm/phy-qcom-qmp.c +@@ -23,6 +23,7 @@ + #include + + #include "phy-qcom-qmp.h" ++#include "phy-qcom-pcie3-qmp.h" + + /* QPHY_SW_RESET bit */ + #define SW_RESET BIT(0) +@@ -812,6 +813,132 @@ static const struct qmp_phy_init_tbl ipq + QMP_PHY_INIT_CFG_L(QPHY_START_CTRL, 0x3), + }; + ++static const struct qmp_phy_init_tbl ipq8074_pcie_gen3_serdes_tbl[] = { ++ QMP_PHY_INIT_CFG(QSERDES_PLL_BIAS_EN_CLKBUFLR_EN, 0x18), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_BIAS_EN_CTRL_BY_PSM, 0x01), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CLK_SELECT, 0x31), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_PLL_IVCO, 0x0f), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_BG_TRIM, 0x0f), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CMN_CONFIG, 0x06), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_LOCK_CMP_EN, 0x42), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_RESETSM_CNTRL, 0x20), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SVS_MODE_CLK_SEL, 0x01), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE_MAP, 0x04), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SVS_MODE_CLK_SEL, 0x05), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE_TIMER1, 0xff), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE_TIMER2, 0x3f), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CORE_CLK_EN, 0x30), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_HSCLK_SEL, 0x21), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DEC_START_MODE0, 0x82), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DIV_FRAC_START3_MODE0, 0x03), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DIV_FRAC_START2_MODE0, 0x355), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DIV_FRAC_START1_MODE0, 0x35555), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_LOCK_CMP2_MODE0, 0x1a), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_LOCK_CMP1_MODE0, 0x1a0a), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CP_CTRL_MODE0, 0xb), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_PLL_RCTRL_MODE0, 0x16), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_PLL_CCTRL_MODE0, 0x28), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_INTEGLOOP_GAIN1_MODE0, 0x0), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_INTEGLOOP_GAIN0_MODE0, 0x40), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE2_MODE0, 0x02), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE1_MODE0, 0x24), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SVS_MODE_CLK_SEL, 0x05), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CORE_CLK_EN, 0x20), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CORECLK_DIV, 0xa), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CLK_SELECT, 0x32), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SYS_CLK_CTRL, 0x02), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SYSCLK_BUF_ENABLE, 0x07), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SYSCLK_EN_SEL, 0x08), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_BG_TIMER, 0xa), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_HSCLK_SEL, 0x1), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DEC_START_MODE1, 0x68), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DIV_FRAC_START3_MODE1, 0x2), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DIV_FRAC_START2_MODE1, 0x2aa), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_DIV_FRAC_START1_MODE1, 0x2aaab), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CLK_ENABLE1, 0x90), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_LOCK_CMP2_MODE1, 0x34), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_LOCK_CMP1_MODE1, 0x3414), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CP_CTRL_MODE1, 0x0b), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_PLL_RCTRL_MODE1, 0x16), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_PLL_CCTRL_MODE1, 0x28), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_INTEGLOOP_GAIN1_MODE1, 0x0), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_INTEGLOOP_GAIN0_MODE1, 0x40), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE2_MODE1, 0x03), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_VCO_TUNE1_MODE1, 0xb4), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_SVS_MODE_CLK_SEL, 0x05), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CORE_CLK_EN, 0x0), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CORECLK_DIV_MODE1, 0x08), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CLK_EP_DIV_MODE0, 0x19), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CLK_EP_DIV_MODE1, 0x28), ++ QMP_PHY_INIT_CFG(QSERDES_PLL_CLK_ENABLE1, 0x90), ++}; ++ ++static const struct qmp_phy_init_tbl ipq8074_pcie_gen3_tx_tbl[] = { ++ QMP_PHY_INIT_CFG(QSERDES_TX0_RES_CODE_LANE_OFFSET_TX, 0x02), ++ QMP_PHY_INIT_CFG(QSERDES_TX0_RCV_DETECT_LVL_2, 0x12), ++ QMP_PHY_INIT_CFG(QSERDES_TX0_HIGHZ_DRVR_EN, 0x10), ++ QMP_PHY_INIT_CFG(QSERDES_TX0_LANE_MODE_1, 0x06), ++}; ++ ++static const struct qmp_phy_init_tbl ipq8074_pcie_gen3_rx_tbl[] = { ++ QMP_PHY_INIT_CFG(QSERDES_RX0_SIGDET_CNTRL, 0x03), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_SIGDET_ENABLES, 0x1c), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_SIGDET_DEGLITCH_CNTRL, 0x14), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL2, 0xe), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL3, 0x4), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_EQU_ADAPTOR_CNTRL4, 0x1b), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_DFE_EN_TIMER, 0x04), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_UCDR_SO_SATURATION_AND_ENABLE, 0x7f), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_UCDR_PI_CONTROLS, 0x70), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x73), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_10_LOW, 0x00), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_10_HIGH, 0x02), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_10_HIGH2, 0xc8), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_10_HIGH3, 0x09), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_10_HIGH4, 0xb1), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_01_LOW, 0x01), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_01_HIGH, 0x02), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_01_HIGH2, 0xc8), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_01_HIGH3, 0x09), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_01_HIGH4, 0xb1), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_00_LOW, 0xf0), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_00_HIGH, 0x2), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_00_HIGH2, 0x2f), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_00_HIGH3, 0xd3), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_MODE_00_HIGH4, 0x40), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_IDAC_TSETTLE_HIGH, 0x00), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_RX_IDAC_TSETTLE_LOW, 0xc0), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_UCDR_FO_GAIN, 0x0c), ++ QMP_PHY_INIT_CFG(QSERDES_RX0_UCDR_SO_GAIN, 0x02), ++}; ++ ++static const struct qmp_phy_init_tbl ipq8074_pcie_gen3_pcs_tbl[] = { ++ QMP_PHY_INIT_CFG(PCS_COM_FLL_CNTRL2, 0x83), ++ QMP_PHY_INIT_CFG(PCS_COM_FLL_CNT_VAL_L, 0x9), ++ QMP_PHY_INIT_CFG(PCS_COM_FLL_CNT_VAL_H_TOL, 0x42), ++ QMP_PHY_INIT_CFG(PCS_COM_FLL_MAN_CODE, 0x40), ++ QMP_PHY_INIT_CFG(PCS_COM_FLL_CNTRL1, 0x01), ++ QMP_PHY_INIT_CFG(PCS_COM_P2U3_WAKEUP_DLY_TIME_AUXCLK_H, 0x0), ++ QMP_PHY_INIT_CFG(PCS_COM_P2U3_WAKEUP_DLY_TIME_AUXCLK_L, 0x1), ++ QMP_PHY_INIT_CFG(PCS_PCIE_OSC_DTCT_ACTIONS, 0x0), ++ QMP_PHY_INIT_CFG(PCS_PCIE_L1P1_WAKEUP_DLY_TIME_AUXCLK_H, 0x00), ++ QMP_PHY_INIT_CFG(PCS_PCIE_L1P1_WAKEUP_DLY_TIME_AUXCLK_L, 0x01), ++ QMP_PHY_INIT_CFG(PCS_PCIE_L1P2_WAKEUP_DLY_TIME_AUXCLK_H, 0x00), ++ QMP_PHY_INIT_CFG(PCS_PCIE_L1P2_WAKEUP_DLY_TIME_AUXCLK_L, 0x01), ++ QMP_PHY_INIT_CFG(PCS_PCIE_EQ_CONFIG1, 0x11), ++ QMP_PHY_INIT_CFG(PCS_PCIE_EQ_CONFIG2, 0xb), ++ QMP_PHY_INIT_CFG(PCS_PCIE_POWER_STATE_CONFIG4, 0x07), ++ QMP_PHY_INIT_CFG(PCS_PCIE_OSC_DTCT_CONFIG2, 0x52), ++ QMP_PHY_INIT_CFG(PCS_PCIE_OSC_DTCT_MODE2_CONFIG2, 0x50), ++ QMP_PHY_INIT_CFG(PCS_PCIE_OSC_DTCT_MODE2_CONFIG4, 0x1a), ++ QMP_PHY_INIT_CFG(PCS_PCIE_OSC_DTCT_MODE2_CONFIG5, 0x6), ++ QMP_PHY_INIT_CFG(PCS_COM_G12S1_TXDEEMPH_M3P5DB, 0x10), ++ QMP_PHY_INIT_CFG(PCS_PCIE_ENDPOINT_REFCLK_DRIVE, 0xc1), ++ QMP_PHY_INIT_CFG(PCS_COM_RX_DCC_CAL_CONFIG, 0x01), ++ QMP_PHY_INIT_CFG(PCS_COM_RX_SIGDET_LVL, 0xaa), ++ QMP_PHY_INIT_CFG(PCS_COM_REFGEN_REQ_CONFIG1, 0x0d), ++}; + static const struct qmp_phy_init_tbl sdm845_qmp_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_V3_COM_BIAS_EN_CLKBUFLR_EN, 0x14), + QMP_PHY_INIT_CFG(QSERDES_V3_COM_CLK_SELECT, 0x30), +@@ -3194,6 +3321,36 @@ static const struct qmp_phy_cfg ipq6018_ + .pwrdn_delay_max = 1005, /* us */ + }; + ++static const struct qmp_phy_cfg ipq8074_pciephy_gen3_cfg = { ++ .type = PHY_TYPE_PCIE, ++ .nlanes = 1, ++ ++ .serdes_tbl = ipq8074_pcie_gen3_serdes_tbl, ++ .serdes_tbl_num = ARRAY_SIZE(ipq8074_pcie_gen3_serdes_tbl), ++ .tx_tbl = ipq8074_pcie_gen3_tx_tbl, ++ .tx_tbl_num = ARRAY_SIZE(ipq8074_pcie_gen3_tx_tbl), ++ .rx_tbl = ipq8074_pcie_gen3_rx_tbl, ++ .rx_tbl_num = ARRAY_SIZE(ipq8074_pcie_gen3_rx_tbl), ++ .pcs_tbl = ipq8074_pcie_gen3_pcs_tbl, ++ .pcs_tbl_num = ARRAY_SIZE(ipq8074_pcie_gen3_pcs_tbl), ++ .clk_list = ipq8074_pciephy_clk_l, ++ .num_clks = ARRAY_SIZE(ipq8074_pciephy_clk_l), ++ .reset_list = ipq8074_pciephy_reset_l, ++ .num_resets = ARRAY_SIZE(ipq8074_pciephy_reset_l), ++ .vreg_list = NULL, ++ .num_vregs = 0, ++ .regs = qmp_v4_usb3phy_regs_layout, ++ ++ .start_ctrl = SERDES_START | PCS_START, ++ .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, ++ ++ .has_phy_com_ctrl = false, ++ .has_lane_rst = false, ++ .has_pwrdn_delay = true, ++ .pwrdn_delay_min = 995, /* us */ ++ .pwrdn_delay_max = 1005, /* us */ ++}; ++ + static const struct qmp_phy_cfg sdm845_qmp_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 1, +@@ -5138,8 +5295,15 @@ static int phy_pipe_clk_register(struct + + init.ops = &clk_fixed_rate_ops; + +- /* controllers using QMP phys use 125MHz pipe clock interface */ +- fixed->fixed_rate = 125000000; ++ /* ++ * controllers using QMP phys use 125MHz pipe clock interface unless ++ * other frequency is specified in dts ++ */ ++ ret = of_property_read_u32(np, "clock-output-rate", ++ (u32 *)&fixed->fixed_rate); ++ if (ret) ++ fixed->fixed_rate = 125000000; ++ + fixed->hw.init = &init; + + ret = devm_clk_hw_register(qmp->dev, &fixed->hw); +@@ -5539,6 +5703,9 @@ static const struct of_device_id qcom_qm + .compatible = "qcom,ipq6018-qmp-usb3-phy", + .data = &ipq8074_usb3phy_cfg, + }, { ++ .compatible = "qcom,ipq8074-qmp-pcie-gen3-phy", ++ .data = &ipq8074_pciephy_gen3_cfg, ++ }, { + .compatible = "qcom,sc7180-qmp-usb3-phy", + .data = &sc7180_usb3phy_cfg, + }, { diff --git a/target/linux/ipq807x/patches-5.15/0104-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch b/target/linux/ipq807x/patches-5.15/0104-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch new file mode 100644 index 000000000..72e3a4086 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0104-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch @@ -0,0 +1,34 @@ +From 110bec39320cbfd23ac869af4aa231cda9c5f74a Mon Sep 17 00:00:00 2001 +From: Kathiravan T +Date: Tue, 8 Feb 2022 21:05:24 +0530 +Subject: [PATCH] arm64: dts: qcom: ipq8074: enable the GICv2m support + +GIC used in the IPQ8074 SoCs has one instance of the GICv2m extension, +which supports upto 32 MSI interrupts. Lets add support for the same. + +Signed-off-by: Kathiravan T +--- + 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 +@@ -669,9 +669,18 @@ + + intc: interrupt-controller@b000000 { + compatible = "qcom,msm-qgic2"; ++ #address-cells = <1>; ++ #size-cells = <1>; + interrupt-controller; + #interrupt-cells = <0x3>; + reg = <0x0b000000 0x1000>, <0x0b002000 0x1000>; ++ ranges = <0 0xb00a000 0xffd>; ++ ++ v2m@0 { ++ compatible = "arm,gic-v2m-frame"; ++ msi-controller; ++ reg = <0x0 0xffd>; ++ }; + }; + + timer { diff --git a/target/linux/ipq807x/patches-5.15/0105-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch b/target/linux/ipq807x/patches-5.15/0105-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch new file mode 100644 index 000000000..0c6064549 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0105-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch @@ -0,0 +1,45 @@ +From dae5693368998da74d3a460da739677c02c9c6af Mon Sep 17 00:00:00 2001 +From: Baruch Siach +Date: Mon, 7 Feb 2022 16:51:24 +0200 +Subject: [PATCH] PCI: dwc: tegra: move GEN3_RELATED DBI register to common + header + +These are common dwc macros that will be used for other platforms. + +Reviewed-by: Rob Herring +Signed-off-by: Baruch Siach +--- + drivers/pci/controller/dwc/pcie-designware.h | 6 ++++++ + drivers/pci/controller/dwc/pcie-tegra194.c | 6 ------ + 2 files changed, 6 insertions(+), 6 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-designware.h ++++ b/drivers/pci/controller/dwc/pcie-designware.h +@@ -74,6 +74,12 @@ + #define PCIE_MSI_INTR0_MASK 0x82C + #define PCIE_MSI_INTR0_STATUS 0x830 + ++#define GEN3_RELATED_OFF 0x890 ++#define GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL BIT(0) ++#define GEN3_RELATED_OFF_GEN3_EQ_DISABLE BIT(16) ++#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_SHIFT 24 ++#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK GENMASK(25, 24) ++ + #define PCIE_PORT_MULTI_LANE_CTRL 0x8C0 + #define PORT_MLTI_UPCFG_SUPPORT BIT(7) + +--- a/drivers/pci/controller/dwc/pcie-tegra194.c ++++ b/drivers/pci/controller/dwc/pcie-tegra194.c +@@ -193,12 +193,6 @@ + #define GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC_MASK GENMASK(23, 8) + #define GEN3_EQ_CONTROL_OFF_FB_MODE_MASK GENMASK(3, 0) + +-#define GEN3_RELATED_OFF 0x890 +-#define GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL BIT(0) +-#define GEN3_RELATED_OFF_GEN3_EQ_DISABLE BIT(16) +-#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_SHIFT 24 +-#define GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK GENMASK(25, 24) +- + #define PORT_LOGIC_AMBA_ERROR_RESPONSE_DEFAULT 0x8D0 + #define AMBA_ERROR_RESPONSE_CRS_SHIFT 3 + #define AMBA_ERROR_RESPONSE_CRS_MASK GENMASK(1, 0) diff --git a/target/linux/ipq807x/patches-5.15/0106-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch b/target/linux/ipq807x/patches-5.15/0106-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch new file mode 100644 index 000000000..51b374d60 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0106-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch @@ -0,0 +1,45 @@ +From 70261569da66997eb4c6057b136af417ed06716e Mon Sep 17 00:00:00 2001 +From: Baruch Siach +Date: Mon, 7 Feb 2022 16:51:25 +0200 +Subject: [PATCH] PCI: qcom: Define slot capabilities using PCI_EXP_SLTCAP_* + +The PCIE_CAP_LINK1_VAL macro actually defines slot capabilities. Use +PCI_EXP_SLTCAP_* macros to spell its value, and rename it to better +describe its meaning. + +Signed-off-by: Baruch Siach +--- + drivers/pci/controller/dwc/pcie-qcom.c | 15 +++++++++++++-- + 1 file changed, 13 insertions(+), 2 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -69,7 +69,18 @@ + #define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c + #define CFG_BRIDGE_SB_INIT BIT(0) + +-#define PCIE_CAP_LINK1_VAL 0x2FD7F ++#define PCIE_CAP_SLOT_POWER_LIMIT_VAL 0x7D00 ++#define PCIE_CAP_SLOT_POWER_LIMIT_SCALE 0x8000 ++#define PCIE_CAP_SLOT_VAL (PCI_EXP_SLTCAP_ABP | \ ++ PCI_EXP_SLTCAP_PCP | \ ++ PCI_EXP_SLTCAP_MRLSP | \ ++ PCI_EXP_SLTCAP_AIP | \ ++ PCI_EXP_SLTCAP_PIP | \ ++ PCI_EXP_SLTCAP_HPS | \ ++ PCI_EXP_SLTCAP_HPC | \ ++ PCI_EXP_SLTCAP_EIP | \ ++ PCIE_CAP_SLOT_POWER_LIMIT_VAL | \ ++ PCIE_CAP_SLOT_POWER_LIMIT_SCALE) + + #define PCIE20_PARF_Q2A_FLUSH 0x1AC + +@@ -1102,7 +1113,7 @@ static int qcom_pcie_init_2_3_3(struct q + + writel(PCI_COMMAND_MASTER, pci->dbi_base + PCI_COMMAND); + writel(DBI_RO_WR_EN, pci->dbi_base + PCIE20_MISC_CONTROL_1_REG); +- writel(PCIE_CAP_LINK1_VAL, pci->dbi_base + offset + PCI_EXP_SLTCAP); ++ writel(PCIE_CAP_SLOT_VAL, pci->dbi_base + offset + PCI_EXP_SLTCAP); + + val = readl(pci->dbi_base + offset + PCI_EXP_LNKCAP); + val &= ~PCI_EXP_LNKCAP_ASPMS; diff --git a/target/linux/ipq807x/patches-5.15/0107-PCI-qcom-Add-IPQ60xx-support.patch b/target/linux/ipq807x/patches-5.15/0107-PCI-qcom-Add-IPQ60xx-support.patch new file mode 100644 index 000000000..20d41e33d --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0107-PCI-qcom-Add-IPQ60xx-support.patch @@ -0,0 +1,212 @@ +From 5d15adca10588019810505a07ed3f6758e4413be Mon Sep 17 00:00:00 2001 +From: Selvam Sathappan Periakaruppan +Date: Thu, 10 Feb 2022 18:02:47 +0100 +Subject: [PATCH] PCI: qcom: Add IPQ60xx support + +IPQ60xx series of SoCs have one port of PCIe gen 3. Add support for that +platform. + +The code is based on downstream[1] Codeaurora kernel v5.4 (branch +win.linuxopenwrt.2.0). + +Split out the DBI registers access part from .init into .post_init. DBI +registers are only accessible after phy_power_on(). + +[1] https://source.codeaurora.org/quic/qsdk/oss/kernel/linux-ipq-5.4/ + +Signed-off-by: Selvam Sathappan Periakaruppan +Signed-off-by: Baruch Siach +--- + drivers/pci/controller/dwc/pcie-designware.h | 1 + + drivers/pci/controller/dwc/pcie-qcom.c | 135 +++++++++++++++++++ + 2 files changed, 136 insertions(+) + +--- a/drivers/pci/controller/dwc/pcie-designware.h ++++ b/drivers/pci/controller/dwc/pcie-designware.h +@@ -76,6 +76,7 @@ + + #define GEN3_RELATED_OFF 0x890 + #define GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL BIT(0) ++#define GEN3_RELATED_OFF_RXEQ_RGRDLESS_RXTS BIT(13) + #define GEN3_RELATED_OFF_GEN3_EQ_DISABLE BIT(16) + #define GEN3_RELATED_OFF_RATE_SHADOW_SEL_SHIFT 24 + #define GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK GENMASK(25, 24) +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -52,6 +52,10 @@ + #define PCIE20_PARF_DBI_BASE_ADDR 0x168 + #define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16C + #define PCIE20_PARF_MHI_CLOCK_RESET_CTRL 0x174 ++#define AHB_CLK_EN BIT(0) ++#define MSTR_AXI_CLK_EN BIT(1) ++#define BYPASS BIT(4) ++ + #define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 + #define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT_V2 0x1A8 + #define PCIE20_PARF_LTSSM 0x1B0 +@@ -179,6 +183,11 @@ struct qcom_pcie_resources_2_7_0 { + struct clk *pipe_clk; + }; + ++struct qcom_pcie_resources_2_9_0 { ++ struct clk_bulk_data clks[5]; ++ struct reset_control *rst; ++}; ++ + union qcom_pcie_resources { + struct qcom_pcie_resources_1_0_0 v1_0_0; + struct qcom_pcie_resources_2_1_0 v2_1_0; +@@ -186,6 +195,7 @@ union qcom_pcie_resources { + struct qcom_pcie_resources_2_3_3 v2_3_3; + struct qcom_pcie_resources_2_4_0 v2_4_0; + struct qcom_pcie_resources_2_7_0 v2_7_0; ++ struct qcom_pcie_resources_2_9_0 v2_9_0; + }; + + struct qcom_pcie; +@@ -1271,6 +1281,121 @@ static void qcom_pcie_post_deinit_2_7_0( + clk_disable_unprepare(res->pipe_clk); + } + ++static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0; ++ struct dw_pcie *pci = pcie->pci; ++ struct device *dev = pci->dev; ++ int ret; ++ ++ res->clks[0].id = "iface"; ++ res->clks[1].id = "axi_m"; ++ res->clks[2].id = "axi_s"; ++ res->clks[3].id = "axi_bridge"; ++ res->clks[4].id = "rchng"; ++ ++ ret = devm_clk_bulk_get(dev, ARRAY_SIZE(res->clks), res->clks); ++ if (ret < 0) ++ return ret; ++ ++ res->rst = devm_reset_control_array_get_exclusive(dev); ++ if (IS_ERR(res->rst)) ++ return PTR_ERR(res->rst); ++ ++ return 0; ++} ++ ++static void qcom_pcie_deinit_2_9_0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0; ++ ++ clk_bulk_disable_unprepare(ARRAY_SIZE(res->clks), res->clks); ++} ++ ++static int qcom_pcie_init_2_9_0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0; ++ struct device *dev = pcie->pci->dev; ++ int ret; ++ ++ ret = reset_control_assert(res->rst); ++ if (ret) { ++ dev_err(dev, "reset assert failed (%d)\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Delay periods before and after reset deassert are working values ++ * from downstream Codeaurora kernel ++ */ ++ usleep_range(2000, 2500); ++ ++ ret = reset_control_deassert(res->rst); ++ if (ret) { ++ dev_err(dev, "reset deassert failed (%d)\n", ret); ++ return ret; ++ } ++ ++ usleep_range(2000, 2500); ++ ++ ret = clk_bulk_prepare_enable(ARRAY_SIZE(res->clks), res->clks); ++ if (ret) ++ goto err_reset; ++ ++ return 0; ++ ++err_reset: ++ reset_control_assert(res->rst); ++ ++ return ret; ++} ++ ++static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) ++{ ++ struct dw_pcie *pci = pcie->pci; ++ u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); ++ u32 val; ++ int i; ++ ++ writel(SLV_ADDR_SPACE_SZ, ++ pcie->parf + PCIE20_v3_PARF_SLV_ADDR_SPACE_SIZE); ++ ++ val = readl(pcie->parf + PCIE20_PARF_PHY_CTRL); ++ val &= ~BIT(0); ++ writel(val, pcie->parf + PCIE20_PARF_PHY_CTRL); ++ ++ writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR); ++ ++ writel(DEVICE_TYPE_RC, pcie->parf + PCIE20_PARF_DEVICE_TYPE); ++ writel(BYPASS | MSTR_AXI_CLK_EN | AHB_CLK_EN, ++ pcie->parf + PCIE20_PARF_MHI_CLOCK_RESET_CTRL); ++ writel(GEN3_RELATED_OFF_RXEQ_RGRDLESS_RXTS ++ | GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL, ++ pci->dbi_base + GEN3_RELATED_OFF); ++ ++ writel(MST_WAKEUP_EN | SLV_WAKEUP_EN | MSTR_ACLK_CGC_DIS ++ | SLV_ACLK_CGC_DIS | CORE_CLK_CGC_DIS | ++ AUX_PWR_DET | L23_CLK_RMV_DIS | L1_CLK_RMV_DIS, ++ pcie->parf + PCIE20_PARF_SYS_CTRL); ++ ++ writel(0, pcie->parf + PCIE20_PARF_Q2A_FLUSH); ++ ++ dw_pcie_dbi_ro_wr_en(pci); ++ writel(PCIE_CAP_SLOT_VAL, pci->dbi_base + offset + PCI_EXP_SLTCAP); ++ ++ val = readl(pci->dbi_base + offset + PCI_EXP_LNKCAP); ++ val &= ~PCI_EXP_LNKCAP_ASPMS; ++ writel(val, pci->dbi_base + offset + PCI_EXP_LNKCAP); ++ ++ writel(PCI_EXP_DEVCTL2_COMP_TMOUT_DIS, pci->dbi_base + offset + ++ PCI_EXP_DEVCTL2); ++ ++ for (i = 0; i < 256; i++) ++ writel(0x0, pcie->parf + PCIE20_PARF_BDF_TO_SID_TABLE_N ++ + (4 * i)); ++ ++ return 0; ++} + static int qcom_pcie_link_up(struct dw_pcie *pci) + { + u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); +@@ -1461,6 +1586,15 @@ static const struct qcom_pcie_ops ops_1_ + .config_sid = qcom_pcie_config_sid_sm8250, + }; + ++/* Qcom IP rev.: 2.9.0 Synopsys IP rev.: 5.00a */ ++static const struct qcom_pcie_ops ops_2_9_0 = { ++ .get_resources = qcom_pcie_get_resources_2_9_0, ++ .init = qcom_pcie_init_2_9_0, ++ .post_init = qcom_pcie_post_init_2_9_0, ++ .deinit = qcom_pcie_deinit_2_9_0, ++ .ltssm_enable = qcom_pcie_2_3_2_ltssm_enable, ++}; ++ + static const struct dw_pcie_ops dw_pcie_ops = { + .link_up = qcom_pcie_link_up, + .start_link = qcom_pcie_start_link, +@@ -1559,6 +1693,7 @@ static const struct of_device_id qcom_pc + { .compatible = "qcom,pcie-qcs404", .data = &ops_2_4_0 }, + { .compatible = "qcom,pcie-sdm845", .data = &ops_2_7_0 }, + { .compatible = "qcom,pcie-sm8250", .data = &ops_1_9_0 }, ++ { .compatible = "qcom,pcie-ipq6018", .data = &ops_2_9_0 }, + { } + }; + diff --git a/target/linux/ipq807x/patches-5.15/0108-PCI-qcom-add-IPQ8074-Gen3-support.patch b/target/linux/ipq807x/patches-5.15/0108-PCI-qcom-add-IPQ8074-Gen3-support.patch new file mode 100644 index 000000000..4cb6c7b4b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0108-PCI-qcom-add-IPQ8074-Gen3-support.patch @@ -0,0 +1,45 @@ +From 4e93203281f4b0c82bf36afd5f316e37991d6456 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 20 Dec 2021 15:01:36 +0100 +Subject: [PATCH] PCI: qcom: add IPQ8074 Gen3 support + +IPQ8074 has one Gen2 and one Gen3 port, Gen3 port is the same one as +in IPQ6018, so reuse the support but just add the missing clocks. + +Signed-off-by: Robert Marko +--- + drivers/pci/controller/dwc/pcie-qcom.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -184,7 +184,7 @@ struct qcom_pcie_resources_2_7_0 { + }; + + struct qcom_pcie_resources_2_9_0 { +- struct clk_bulk_data clks[5]; ++ struct clk_bulk_data clks[7]; + struct reset_control *rst; + }; + +@@ -1291,8 +1291,10 @@ static int qcom_pcie_get_resources_2_9_0 + res->clks[0].id = "iface"; + res->clks[1].id = "axi_m"; + res->clks[2].id = "axi_s"; +- res->clks[3].id = "axi_bridge"; +- res->clks[4].id = "rchng"; ++ res->clks[3].id = "ahb"; ++ res->clks[4].id = "aux"; ++ res->clks[5].id = "axi_bridge"; ++ res->clks[6].id = "rchng"; + + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(res->clks), res->clks); + if (ret < 0) +@@ -1694,6 +1696,7 @@ static const struct of_device_id qcom_pc + { .compatible = "qcom,pcie-sdm845", .data = &ops_2_7_0 }, + { .compatible = "qcom,pcie-sm8250", .data = &ops_1_9_0 }, + { .compatible = "qcom,pcie-ipq6018", .data = &ops_2_9_0 }, ++ { .compatible = "qcom,pcie-ipq8074-gen3", .data = &ops_2_9_0 }, + { } + }; + diff --git a/target/linux/ipq807x/patches-5.15/0109-PCI-qcom-fix-IPQ8074-Gen2-support.patch b/target/linux/ipq807x/patches-5.15/0109-PCI-qcom-fix-IPQ8074-Gen2-support.patch new file mode 100644 index 000000000..9676bb42b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0109-PCI-qcom-fix-IPQ8074-Gen2-support.patch @@ -0,0 +1,95 @@ +From 68de01687bdd59cb6ca4999743166e549fb91856 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 20 Dec 2021 15:06:03 +0100 +Subject: [PATCH] PCI: qcom: fix IPQ8074 Gen2 support + +IPQ8074 has one Gen2 and one Gen3 port, currently the Gen2 port will +cause the system to hang as its using DBI registers in the .init +and those are only accesible after phy_power_on(). + +So solve this by splitting the DBI read/writes to .post_init. + +Signed-off-by: Robert Marko +--- + drivers/pci/controller/dwc/pcie-qcom.c | 48 +++++++++++++++----------- + 1 file changed, 28 insertions(+), 20 deletions(-) + +--- a/drivers/pci/controller/dwc/pcie-qcom.c ++++ b/drivers/pci/controller/dwc/pcie-qcom.c +@@ -1047,9 +1047,7 @@ static int qcom_pcie_init_2_3_3(struct q + struct qcom_pcie_resources_2_3_3 *res = &pcie->res.v2_3_3; + struct dw_pcie *pci = pcie->pci; + struct device *dev = pci->dev; +- u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + int i, ret; +- u32 val; + + for (i = 0; i < ARRAY_SIZE(res->rst); i++) { + ret = reset_control_assert(res->rst[i]); +@@ -1106,6 +1104,33 @@ static int qcom_pcie_init_2_3_3(struct q + goto err_clk_aux; + } + ++ return 0; ++ ++err_clk_aux: ++ clk_disable_unprepare(res->ahb_clk); ++err_clk_ahb: ++ clk_disable_unprepare(res->axi_s_clk); ++err_clk_axi_s: ++ clk_disable_unprepare(res->axi_m_clk); ++err_clk_axi_m: ++ clk_disable_unprepare(res->iface); ++err_clk_iface: ++ /* ++ * Not checking for failure, will anyway return ++ * the original failure in 'ret'. ++ */ ++ for (i = 0; i < ARRAY_SIZE(res->rst); i++) ++ reset_control_assert(res->rst[i]); ++ ++ return ret; ++} ++ ++static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) ++{ ++ struct dw_pcie *pci = pcie->pci; ++ u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); ++ u32 val; ++ + writel(SLV_ADDR_SPACE_SZ, + pcie->parf + PCIE20_v3_PARF_SLV_ADDR_SPACE_SIZE); + +@@ -1133,24 +1158,6 @@ static int qcom_pcie_init_2_3_3(struct q + PCI_EXP_DEVCTL2); + + return 0; +- +-err_clk_aux: +- clk_disable_unprepare(res->ahb_clk); +-err_clk_ahb: +- clk_disable_unprepare(res->axi_s_clk); +-err_clk_axi_s: +- clk_disable_unprepare(res->axi_m_clk); +-err_clk_axi_m: +- clk_disable_unprepare(res->iface); +-err_clk_iface: +- /* +- * Not checking for failure, will anyway return +- * the original failure in 'ret'. +- */ +- for (i = 0; i < ARRAY_SIZE(res->rst); i++) +- reset_control_assert(res->rst[i]); +- +- return ret; + } + + static int qcom_pcie_get_resources_2_7_0(struct qcom_pcie *pcie) +@@ -1563,6 +1570,7 @@ static const struct qcom_pcie_ops ops_2_ + static const struct qcom_pcie_ops ops_2_3_3 = { + .get_resources = qcom_pcie_get_resources_2_3_3, + .init = qcom_pcie_init_2_3_3, ++ .post_init = qcom_pcie_post_init_2_3_3, + .deinit = qcom_pcie_deinit_2_3_3, + .ltssm_enable = qcom_pcie_2_3_2_ltssm_enable, + }; diff --git a/target/linux/ipq807x/patches-5.15/0110-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch b/target/linux/ipq807x/patches-5.15/0110-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch new file mode 100644 index 000000000..b79f1bd7a --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0110-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch @@ -0,0 +1,193 @@ +From 9e280276de874970d03cdc124d8bfa7afbb6aef1 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 20 Dec 2021 15:08:04 +0100 +Subject: [PATCH] arm64: dts: ipq8074: fix PCI related DT nodes + +Currently present PCI PHY and PCI controller nodes are not working +and are incorrect for the v2 of IPQ8074 which is the only version +supported upstream. + +So, correct the PCI related nodes. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 73 +++++++++++++++------------ + 1 file changed, 42 insertions(+), 31 deletions(-) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -228,9 +228,9 @@ + status = "disabled"; + }; + +- pcie_qmp0: phy@86000 { +- compatible = "qcom,ipq8074-qmp-pcie-phy"; +- reg = <0x00086000 0x1000>; ++ pcie_qmp0: phy@84000 { ++ compatible = "qcom,ipq8074-qmp-pcie-gen3-phy"; ++ reg = <0x00084000 0x1bc>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -244,21 +244,22 @@ + "common"; + status = "disabled"; + +- pcie_phy0: phy@86200 { +- reg = <0x86200 0x16c>, +- <0x86400 0x200>, +- <0x86800 0x4f4>; ++ pcie_phy0: phy@84200 { ++ reg = <0x84200 0x16c>, ++ <0x84400 0x200>, ++ <0x84800 0x4f4>; + #phy-cells = <0>; + #clock-cells = <0>; + clocks = <&gcc GCC_PCIE0_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "pcie_0_pipe_clk"; ++ clock-output-rate = <250000000>; + }; + }; + + pcie_qmp1: phy@8e000 { + compatible = "qcom,ipq8074-qmp-pcie-phy"; +- reg = <0x0008e000 0x1000>; ++ reg = <0x0008e000 0x1c4>; + #address-cells = <1>; + #size-cells = <1>; + ranges; +@@ -273,14 +274,15 @@ + status = "disabled"; + + pcie_phy1: phy@8e200 { +- reg = <0x8e200 0x16c>, ++ reg = <0x8e200 0x130>, + <0x8e400 0x200>, +- <0x8e800 0x4f4>; ++ <0x8e800 0x1f8>; + #phy-cells = <0>; + #clock-cells = <0>; + clocks = <&gcc GCC_PCIE1_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "pcie_1_pipe_clk"; ++ clock-output-rate = <125000000>; + }; + }; + +@@ -676,7 +678,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>; +@@ -769,6 +771,7 @@ + linux,pci-domain = <1>; + bus-range = <0x00 0xff>; + num-lanes = <1>; ++ max-link-speed = <2>; + #address-cells = <3>; + #size-cells = <2>; + +@@ -776,12 +779,12 @@ + phy-names = "pciephy"; + + ranges = <0x81000000 0 0x10200000 0x10200000 +- 0 0x100000 /* downstream I/O */ +- 0x82000000 0 0x10300000 0x10300000 +- 0 0xd00000>; /* non-prefetchable memory */ ++ 0 0x10000>, /* downstream I/O */ ++ <0x82000000 0 0x10220000 0x10220000 ++ 0 0xfde0000>; /* non-prefetchable memory */ ++ ++ msi-parent = <&gic_v2m0>; + +- interrupts = ; +- interrupt-names = "msi"; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 142 +@@ -821,16 +824,18 @@ + }; + + pcie0: pci@20000000 { +- compatible = "qcom,pcie-ipq8074"; +- reg = <0x20000000 0xf1d>, +- <0x20000f20 0xa8>, +- <0x00080000 0x2000>, +- <0x20100000 0x1000>; +- reg-names = "dbi", "elbi", "parf", "config"; ++ compatible = "qcom,pcie-ipq8074-gen3"; ++ reg = <0x20000000 0xf1d>, ++ <0x20000f20 0xa8>, ++ <0x20001000 0x1000>, ++ <0x00080000 0x4000>, ++ <0x20100000 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>; + +@@ -838,12 +843,12 @@ + phy-names = "pciephy"; + + ranges = <0x81000000 0 0x20200000 0x20200000 +- 0 0x100000 /* downstream I/O */ +- 0x82000000 0 0x20300000 0x20300000 +- 0 0xd00000>; /* non-prefetchable memory */ ++ 0 0x10000>, /* downstream I/O */ ++ <0x82000000 0 0x20220000 0x20220000 ++ 0 0xfde0000>; /* non-prefetchable memory */ ++ ++ msi-parent = <&gic_v2m0>; + +- interrupts = ; +- interrupt-names = "msi"; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 0x7>; + interrupt-map = <0 0 0 1 &intc 0 75 +@@ -859,27 +864,33 @@ + <&gcc GCC_PCIE0_AXI_M_CLK>, + <&gcc GCC_PCIE0_AXI_S_CLK>, + <&gcc GCC_PCIE0_AHB_CLK>, +- <&gcc GCC_PCIE0_AUX_CLK>; ++ <&gcc GCC_PCIE0_AUX_CLK>, ++ <&gcc GCC_PCIE0_AXI_S_BRIDGE_CLK>, ++ <&gcc GCC_PCIE0_RCHNG_CLK>; + + clock-names = "iface", + "axi_m", + "axi_s", + "ahb", +- "aux"; ++ "aux", ++ "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_MASTER_STICKY_ARES>, ++ <&gcc GCC_PCIE0_AXI_SLAVE_STICKY_ARES>; + reset-names = "pipe", + "sleep", + "sticky", + "axi_m", + "axi_s", + "ahb", +- "axi_m_sticky"; ++ "axi_m_sticky", ++ "axi_s_sticky"; + status = "disabled"; + }; + }; diff --git a/target/linux/ipq807x/patches-5.15/0111-remoteproc-qcom-Add-PRNG-proxy-clock.patch b/target/linux/ipq807x/patches-5.15/0111-remoteproc-qcom-Add-PRNG-proxy-clock.patch new file mode 100644 index 000000000..f26651682 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0111-remoteproc-qcom-Add-PRNG-proxy-clock.patch @@ -0,0 +1,155 @@ +From ddc957c5eee78cf41d04040b6de3e3437830b473 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +--- + 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 */ +@@ -732,6 +741,7 @@ static int q6v5_wcss_stop(struct rproc * + return ret; + } + ++ clk_disable_unprepare(wcss->prng_clk); + qcom_q6v5_unprepare(&wcss->q6v5); + + return 0; +@@ -896,7 +906,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; + +@@ -986,7 +1010,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)) +@@ -1030,12 +1054,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; + } +@@ -1082,6 +1108,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, +@@ -1091,6 +1118,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/ipq807x/patches-5.15/0112-remoteproc-qcom-Add-secure-PIL-support.patch b/target/linux/ipq807x/patches-5.15/0112-remoteproc-qcom-Add-secure-PIL-support.patch new file mode 100644 index 000000000..cb675f59b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0112-remoteproc-qcom-Add-secure-PIL-support.patch @@ -0,0 +1,143 @@ +From 3151bf7eb1350e3dd8a51424942d7365673a6e25 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +--- + 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 + #include + #include ++#include + #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"); +@@ -717,6 +733,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); +@@ -741,6 +766,7 @@ static int q6v5_wcss_stop(struct rproc * + return ret; + } + ++pas_done: + clk_disable_unprepare(wcss->prng_clk); + qcom_q6v5_unprepare(&wcss->q6v5); + +@@ -764,9 +790,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; + +@@ -1032,6 +1064,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) { +@@ -1045,6 +1080,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) +@@ -1115,6 +1151,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/ipq807x/patches-5.15/0113-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch b/target/linux/ipq807x/patches-5.15/0113-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch new file mode 100644 index 000000000..2e0b45b7e --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0113-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch @@ -0,0 +1,103 @@ +From 0915eaecd5e06227c9e4e3a4a931c45942e7b4ed Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +--- + 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; +@@ -788,8 +790,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, +@@ -1068,7 +1091,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; +@@ -1081,6 +1104,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) +@@ -1145,7 +1169,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, +@@ -1158,7 +1183,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/ipq807x/patches-5.15/0114-remoteproc-qcom-Add-ssr-subdevice-identifier.patch b/target/linux/ipq807x/patches-5.15/0114-remoteproc-qcom-Add-ssr-subdevice-identifier.patch new file mode 100644 index 000000000..2ac54a73d --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0114-remoteproc-qcom-Add-ssr-subdevice-identifier.patch @@ -0,0 +1,24 @@ +From a0774816e1e76c47fe47a4e0fa7e0a84811dd62f Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +--- + 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 +@@ -1174,6 +1174,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/ipq807x/patches-5.15/0115-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch b/target/linux/ipq807x/patches-5.15/0115-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch new file mode 100644 index 000000000..2c353238f --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0115-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch @@ -0,0 +1,79 @@ +From e99af92362058cfec70569057c1b15da9a1acb5f Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +--- + 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; +@@ -874,10 +875,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; +@@ -925,9 +929,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; + } +@@ -1174,6 +1178,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, +@@ -1188,6 +1193,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/ipq807x/patches-5.15/0116-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch b/target/linux/ipq807x/patches-5.15/0116-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch new file mode 100644 index 000000000..8e1dbc033 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0116-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch @@ -0,0 +1,25 @@ +From 25e8ae5b960f45e9e19aa24cc023603375f44db0 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +Acked-by: Rob Herring +Acked-by: Stephen Boyd +--- + 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 +@@ -366,5 +366,6 @@ + #define GCC_PCIE1_AHB_ARES 129 + #define GCC_PCIE1_AXI_MASTER_STICKY_ARES 130 + #define GCC_PCIE0_AXI_SLAVE_STICKY_ARES 131 ++#define GCC_WCSSAON_RESET 132 + + #endif diff --git a/target/linux/ipq807x/patches-5.15/0117-clk-qcom-Add-WCSSAON-reset.patch b/target/linux/ipq807x/patches-5.15/0117-clk-qcom-Add-WCSSAON-reset.patch new file mode 100644 index 000000000..2ef66629e --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0117-clk-qcom-Add-WCSSAON-reset.patch @@ -0,0 +1,25 @@ +From 1a279695f52e8ab438fde5dbfbb762da8980e037 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +Acked-by: Stephen Boyd +--- + 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 +@@ -4744,6 +4744,7 @@ 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_WCSSAON_RESET] = { 0x59010, 0 }, + }; + + static const struct of_device_id gcc_ipq8074_match_table[] = { diff --git a/target/linux/ipq807x/patches-5.15/0118-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch b/target/linux/ipq807x/patches-5.15/0118-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch new file mode 100644 index 000000000..b6bb23914 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0118-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch @@ -0,0 +1,47 @@ +From 76126149a2a76a0cf9895224dbb4dacf69ebb06a Mon Sep 17 00:00:00 2001 +From: Sivaprakash Murugesan +Date: Fri, 17 Apr 2020 16:37:10 +0530 +Subject: [PATCH] remoteproc: wcss: disable auto boot for IPQ8074 + +auto boot is disabled for IPQ8074 the wifi driver brings up the wcss. + +Signed-off-by: Sivaprakash Murugesan +Change-Id: Ia82edb7ee52f2bd010c099f151179d69a953ac88 +--- + 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) +@@ -1147,6 +1148,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; +@@ -1183,6 +1185,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 = { +@@ -1199,6 +1202,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/ipq807x/patches-5.15/0119-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch b/target/linux/ipq807x/patches-5.15/0119-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch new file mode 100644 index 000000000..15cbd86cb --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0119-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch @@ -0,0 +1,126 @@ +From 1078ef8be64de03a2dbd78d17c87af0472748079 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +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 +Signed-off-by: Sricharan R +Signed-off-by: Nikhil Prakash V +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 88 +++++++++++++++++++++++++++ + 1 file changed, 88 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -136,6 +136,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>; +@@ -393,6 +419,11 @@ + #hwlock-cells = <1>; + }; + ++ tcsr_q6: syscon@1945000 { ++ compatible = "syscon"; ++ reg = <0x01945000 0xe000>; ++ }; ++ + spmi_bus: spmi@200f000 { + compatible = "qcom,spmi-pmic-arb"; + reg = <0x0200f000 0x001000>, +@@ -893,5 +924,62 @@ + "axi_s_sticky"; + status = "disabled"; + }; ++ ++ apcs_glb: mailbox@b111000 { ++ compatible = "qcom,ipq8074-apcs-apps-global"; ++ reg = <0x0b111000 0x1000>; ++ ++ #mbox-cells = <1>; ++ }; ++ ++ 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 = ; ++ qcom,remote-pid = <1>; ++ mboxes = <&apcs_glb 8>; ++ ++ rpm_requests { ++ qcom,glink-channels = "IPCRTR"; ++ }; ++ }; ++ }; + }; + }; diff --git a/target/linux/ipq807x/patches-5.15/0120-arm64-dts-ipq8074-Add-WLAN-node.patch b/target/linux/ipq807x/patches-5.15/0120-arm64-dts-ipq8074-Add-WLAN-node.patch new file mode 100644 index 000000000..ca22d048b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0120-arm64-dts-ipq8074-Add-WLAN-node.patch @@ -0,0 +1,134 @@ +From 4645129811d46fbd18100e0b76fffe88795a54da Mon Sep 17 00:00:00 2001 +From: Robert Marko +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 +--- + 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 +@@ -981,5 +981,116 @@ + }; + }; + }; ++ ++ wifi: wifi@c0000000 { ++ compatible = "qcom,ipq8074-wifi"; ++ reg = <0xc000000 0x2000000>; ++ ++ interrupts = , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ ; ++ ++ 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"; ++ }; + }; + }; diff --git a/target/linux/ipq807x/patches-5.15/0121-thermal-qcom-tsens-Add-IPQ8074-support.patch b/target/linux/ipq807x/patches-5.15/0121-thermal-qcom-tsens-Add-IPQ8074-support.patch new file mode 100644 index 000000000..e55bc0bdb --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0121-thermal-qcom-tsens-Add-IPQ8074-support.patch @@ -0,0 +1,99 @@ +From b7bf74840dcffd209d4fc26a6d16d669bcda8f1d Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 20 Nov 2020 13:52:43 +0100 +Subject: [PATCH] thermal: qcom: tsens: Add IPQ8074 support + +Qualcomm IPQ807x SoC-s use tsens v2.3.0 IP, but they +only have one interrupt and not a dedicated critical interrupt. + +Signed-off-by: Robert Marko +--- + drivers/thermal/qcom/tsens-v2.c | 14 ++++++++++++++ + drivers/thermal/qcom/tsens.c | 27 ++++++++++++++++++--------- + drivers/thermal/qcom/tsens.h | 2 +- + 3 files changed, 33 insertions(+), 10 deletions(-) + +--- a/drivers/thermal/qcom/tsens-v2.c ++++ b/drivers/thermal/qcom/tsens-v2.c +@@ -36,6 +36,14 @@ static struct tsens_features tsens_v2_fe + .max_sensors = 16, + }; + ++static struct tsens_features tsens_ipq8074_feat = { ++ .ver_major = VER_2_X, ++ .crit_int = 0, ++ .adc = 0, ++ .srot_split = 1, ++ .max_sensors = 16, ++}; ++ + static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = { + /* ----- SROT ------ */ + /* VERSION */ +@@ -101,6 +109,12 @@ struct tsens_plat_data data_tsens_v2 = { + .fields = tsens_v2_regfields, + }; + ++struct tsens_plat_data data_tsens_ipq8074 = { ++ .ops = &ops_generic_v2, ++ .feat = &tsens_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 +@@ -325,16 +325,22 @@ static int tsens_read_irq_state(struct t + ret = regmap_field_read(priv->rf[LOW_INT_MASK_0 + hw_id], &d->low_irq_mask); + if (ret) + return ret; +- ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id], +- &d->crit_irq_clear); +- if (ret) +- return ret; +- ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id], +- &d->crit_irq_mask); +- if (ret) +- return ret; ++ if (priv->feat->crit_int) { ++ ret = regmap_field_read(priv->rf[CRIT_INT_CLEAR_0 + hw_id], ++ &d->crit_irq_clear); ++ if (ret) ++ return ret; ++ ret = regmap_field_read(priv->rf[CRIT_INT_MASK_0 + hw_id], ++ &d->crit_irq_mask); ++ if (ret) ++ return ret; + +- d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id); ++ d->crit_thresh = tsens_hw_to_mC(s, CRIT_THRESH_0 + hw_id); ++ } else { ++ d->crit_irq_clear = 0; ++ d->crit_irq_mask = 0; ++ d->crit_thresh = 0; ++ } + } else { + /* No mask register on older TSENS */ + d->up_irq_mask = 0; +@@ -993,6 +999,9 @@ static const struct of_device_id tsens_t + }, { + .compatible = "qcom,tsens-v2", + .data = &data_tsens_v2, ++ }, { ++ .compatible = "qcom,ipq8074-tsens", ++ .data = &data_tsens_ipq8074, + }, + {} + }; +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -593,6 +593,6 @@ extern struct tsens_plat_data data_8916, + extern struct tsens_plat_data data_tsens_v1, data_8976; + + /* TSENS v2 targets */ +-extern struct tsens_plat_data data_8996, data_tsens_v2; ++extern struct tsens_plat_data data_8996, data_tsens_v2, data_tsens_ipq8074; + + #endif /* __QCOM_TSENS_H__ */ diff --git a/target/linux/ipq807x/patches-5.15/0122-arm64-dts-ipq8074-add-thermal-nodes.patch b/target/linux/ipq807x/patches-5.15/0122-arm64-dts-ipq8074-add-thermal-nodes.patch new file mode 100644 index 000000000..bd991c08b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0122-arm64-dts-ipq8074-add-thermal-nodes.patch @@ -0,0 +1,128 @@ +From bf718a63d90b0921064892fa0e4dcf3db5233b1a Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 24 Dec 2021 20:33:59 +0100 +Subject: [PATCH] arm64: dts: 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 +--- + 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 +@@ -332,6 +332,16 @@ + status = "disabled"; + }; + ++ tsens: thermal-sensor@4a9000 { ++ compatible = "qcom,ipq8074-tsens"; ++ reg = <0x4a9000 0x1000>, /* TM */ ++ <0x4a8000 0x1000>; /* SROT */ ++ interrupts = ; ++ interrupt-names = "uplow"; ++ #qcom,sensors = <16>; ++ #thermal-sensor-cells = <1>; ++ }; ++ + cryptobam: dma@704000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x00704000 0x20000>; +@@ -1093,4 +1103,90 @@ + status = "disabled"; + }; + }; ++ ++ 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/ipq807x/patches-5.15/0123-regulator-qcom_spmi-Add-PMD9655-SPMI-regulator.patch b/target/linux/ipq807x/patches-5.15/0123-regulator-qcom_spmi-Add-PMD9655-SPMI-regulator.patch new file mode 100644 index 000000000..5093054c2 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0123-regulator-qcom_spmi-Add-PMD9655-SPMI-regulator.patch @@ -0,0 +1,38 @@ +From cb5dc874a8f0740eb988c2851a97d214e463eeb1 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Tue, 31 Mar 2020 22:00:27 +0530 +Subject: [PATCH] regulator: qcom_spmi: Add PMD9655 SPMI regulator + +PMD9655 is used in IPQ8074 and provides S3 for cores, +S4 for UBI core and LDO11 for SDIO/eMMC. + +Signed-off-by: Praveenkumar I +Signed-off-by: Robert Marko +--- + drivers/regulator/qcom_spmi-regulator.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -2092,6 +2092,13 @@ static const struct spmi_regulator_data + { } + }; + ++static const struct spmi_regulator_data pmd9655_regulators[] = { ++ { "s3", 0x1a00, "vdd_s3",}, ++ { "s4", 0x1d00, "vdd_s4",}, ++ { "ldo11", 0x4a00, "vdd_ldo11",}, ++ { } ++}; ++ + static const struct of_device_id qcom_spmi_regulator_match[] = { + { .compatible = "qcom,pm8004-regulators", .data = &pm8004_regulators }, + { .compatible = "qcom,pm8005-regulators", .data = &pm8005_regulators }, +@@ -2104,6 +2111,7 @@ static const struct of_device_id qcom_sp + { .compatible = "qcom,pm660-regulators", .data = &pm660_regulators }, + { .compatible = "qcom,pm660l-regulators", .data = &pm660l_regulators }, + { .compatible = "qcom,pms405-regulators", .data = &pms405_regulators }, ++ { .compatible = "qcom,pmd9655-regulators", .data = &pmd9655_regulators }, + { } + }; + MODULE_DEVICE_TABLE(of, qcom_spmi_regulator_match); diff --git a/target/linux/ipq807x/patches-5.15/0124-regulator-qcom_spmi-SMPS-range-is-added-to-support-P.patch b/target/linux/ipq807x/patches-5.15/0124-regulator-qcom_spmi-SMPS-range-is-added-to-support-P.patch new file mode 100644 index 000000000..1b8a5c429 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0124-regulator-qcom_spmi-SMPS-range-is-added-to-support-P.patch @@ -0,0 +1,22 @@ +From 3c5e2d0c4149c287b9992e156a15ff881895bf00 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Mon, 4 May 2020 19:31:00 +0530 +Subject: [PATCH] regulator: qcom_spmi: SMPS range is added to support PMD9655 + PMIC + +Signed-off-by: Praveenkumar I +Change-Id: I5571801debec25527fd763d95aff27cc42f53bde +--- + drivers/regulator/qcom_spmi-regulator.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -481,6 +481,7 @@ static struct spmi_voltage_range ln_ldo_ + }; + + static struct spmi_voltage_range smps_ranges[] = { ++ SPMI_VOLTAGE_RANGE(2, 670000, 670000, 990000, 990000, 8000), + SPMI_VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500), + SPMI_VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 3125000, 25000), + }; diff --git a/target/linux/ipq807x/patches-5.15/0125-regulator-qcom_spmi-Initialize-slew-rate-only-if-req.patch b/target/linux/ipq807x/patches-5.15/0125-regulator-qcom_spmi-Initialize-slew-rate-only-if-req.patch new file mode 100644 index 000000000..9fd7dc432 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0125-regulator-qcom_spmi-Initialize-slew-rate-only-if-req.patch @@ -0,0 +1,34 @@ +From 7475c4cd8898ba5bdf3021a23d3087d7a9747ec4 Mon Sep 17 00:00:00 2001 +From: PRAVEENKUMAR I +Date: Tue, 5 May 2020 07:57:21 +0530 +Subject: [PATCH] regulator: qcom_spmi: Initialize slew rate only if required + +Initialize slew rate only if set_voltage_time_sel in ops +is defined. + +Change-Id: I661c88d2f4a8f26cc85b1e2d4c8aa3170420ba6c +Signed-off-by: Rajith Cherian +(cherry picked from commit 608a6f171ef4017197fbe2069b5910b582923027) +Signed-off-by: Praveenkumar I + +Change-Id: Ida3cf3d754e1207e34a164d6d86c6e1aa109ef1e +--- + drivers/regulator/qcom_spmi-regulator.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -1617,6 +1617,13 @@ static int spmi_regulator_init_slew_rate + int step, delay, slew_rate, step_delay; + const struct spmi_voltage_range *range; + ++ /* ++ * Slew rate need not be initialized if ++ * set_voltage_time_sel in the ops is not defined. ++ */ ++ if (!vreg->desc.ops->set_voltage_time_sel) ++ return 0; ++ + ret = spmi_vreg_read(vreg, SPMI_COMMON_REG_STEP_CTRL, ®, 1); + if (ret) { + dev_err(vreg->dev, "spmi read failed, ret=%d\n", ret); diff --git a/target/linux/ipq807x/patches-5.15/0126-regulator-qcom_spmi-Add-support-for-VMPWM_CTL-subtyp.patch b/target/linux/ipq807x/patches-5.15/0126-regulator-qcom_spmi-Add-support-for-VMPWM_CTL-subtyp.patch new file mode 100644 index 000000000..9a15cb7ba --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0126-regulator-qcom_spmi-Add-support-for-VMPWM_CTL-subtyp.patch @@ -0,0 +1,159 @@ +From 29d356f93a44cd96a2618ffcc08968a1a0dff828 Mon Sep 17 00:00:00 2001 +From: PRAVEENKUMAR I +Date: Tue, 5 May 2020 07:57:52 +0530 +Subject: [PATCH] regulator: qcom_spmi: Add support for VMPWM_CTL subtype + +Support for Voltage Mode PWM Controller (VMPWM_CTL). +Set/Get microvolts functions added. Function to find the +voltage range for this particular subtype added. + +Change-Id: Ib4bf35ee65de17a917f01e63208368c7770401d4 +Signed-off-by: Rajith Cherian +(cherry picked from commit 31e7e4183b5afaf18dbca3548f4988c420ebb58b) +Signed-off-by: Praveenkumar I + +Change-Id: Id7a3caef84499b9e2eefda9f57576923c84234f0 +--- + drivers/regulator/qcom_spmi-regulator.c | 82 +++++++++++++++++++++++++ + 1 file changed, 82 insertions(+) + +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -164,6 +164,7 @@ enum spmi_regulator_subtype { + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL3 = 0x0f, + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL4 = 0x10, + SPMI_REGULATOR_SUBTYPE_HFS430 = 0x0a, ++ SPMI_REGULATOR_SUBTYPE_VMPWM_CTL = 0x0a, + }; + + enum spmi_common_regulator_registers { +@@ -289,6 +290,10 @@ enum spmi_common_control_register_index + #define SPMI_FTSMPS_STEP_CTRL_DELAY_MASK 0x07 + #define SPMI_FTSMPS_STEP_CTRL_DELAY_SHIFT 0 + ++#define SPMI_SMPS_VMPWM_VSET_UB_SHIFT 8 ++#define SPMI_SMPS_VMPWM_VSET_UB_MASK 0xf00 ++#define SPMI_SMPS_VMPWM_VSET_LB_MASK 0xff ++ + /* Clock rate in kHz of the FTSMPS regulator reference clock. */ + #define SPMI_FTSMPS_CLOCK_RATE 19200 + +@@ -486,6 +491,10 @@ static struct spmi_voltage_range smps_ra + SPMI_VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 3125000, 25000), + }; + ++static struct spmi_voltage_range smps_vmpwm_ranges[] = { ++ SPMI_VOLTAGE_RANGE(0, 664000, 664000, 1104000, 1104000, 8000), ++}; ++ + static struct spmi_voltage_range ftsmps_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 0, 350000, 1275000, 1275000, 5000), + SPMI_VOLTAGE_RANGE(1, 0, 1280000, 2040000, 2040000, 10000), +@@ -551,6 +560,7 @@ static DEFINE_SPMI_SET_POINTS(nldo2); + static DEFINE_SPMI_SET_POINTS(nldo3); + static DEFINE_SPMI_SET_POINTS(ln_ldo); + static DEFINE_SPMI_SET_POINTS(smps); ++static DEFINE_SPMI_SET_POINTS(smps_vmpwm); + static DEFINE_SPMI_SET_POINTS(ftsmps); + static DEFINE_SPMI_SET_POINTS(ftsmps2p5); + static DEFINE_SPMI_SET_POINTS(ftsmps426); +@@ -745,6 +755,24 @@ spmi_regulator_find_range(struct spmi_re + return NULL; + } + ++static const struct spmi_voltage_range * ++spmi_regulator_find_uV_range(struct spmi_regulator *vreg, int min, int max) ++{ ++ const struct spmi_voltage_range *range, *end; ++ ++ if (!vreg->set_points || !vreg->set_points->count) ++ return 0; ++ ++ range = vreg->set_points->range; ++ end = range + vreg->set_points->count; ++ ++ for (; range < end; range++) ++ if ((range->min_uV <= min) && (range->max_uV >= max)) ++ return range; ++ ++ return 0; ++} ++ + static int spmi_regulator_select_voltage_same_range(struct spmi_regulator *vreg, + int min_uV, int max_uV) + { +@@ -966,6 +994,47 @@ static int spmi_regulator_ult_lo_smps_ge + return spmi_hw_selector_to_sw(vreg, voltage_sel, range); + } + ++static int spmi_regulator_smps_vmpwm_set_vol_uV(struct regulator_dev *rdev, ++ int min_uV, int max_uV, unsigned *selector) ++{ ++ struct spmi_regulator *vreg = rdev_get_drvdata(rdev); ++ const struct spmi_voltage_range *range; ++ int req_vol; ++ u8 reg[2]; ++ ++ range = spmi_regulator_find_uV_range(vreg, min_uV, max_uV); ++ if (!range) ++ return -EINVAL; ++ ++ *selector = spmi_regulator_select_voltage(vreg, min_uV, max_uV); ++ req_vol = range->set_point_min_uV + (range->step_uV * (*selector)); ++ ++ /* Convert uV to mV as the register supports mV */ ++ req_vol = req_vol/1000; ++ ++ /* ++ * Voltage set point bits<7:0>. 2-Byte Word (lower byte word) ++ */ ++ reg[0] = req_vol & SPMI_SMPS_VMPWM_VSET_LB_MASK; ++ /* ++ * Voltage set point bit <11:8>. 2-Byte Word (upper byte word) ++ */ ++ reg[1] = (req_vol & SPMI_SMPS_VMPWM_VSET_UB_MASK) ++ >> SPMI_SMPS_VMPWM_VSET_UB_SHIFT; ++ ++ return spmi_vreg_write(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, reg, 2); ++} ++ ++static int spmi_regulator_smps_vmpwm_get_vol_uV(struct regulator_dev *rdev) ++{ ++ struct spmi_regulator *vreg = rdev_get_drvdata(rdev); ++ u8 reg[2]; ++ ++ spmi_vreg_read(vreg, SPMI_COMMON_REG_VOLTAGE_RANGE, reg, 2); ++ ++ return ((reg[1] << SPMI_SMPS_VMPWM_VSET_UB_SHIFT) | reg[0]) * 1000; ++} ++ + static int spmi_regulator_common_list_voltage(struct regulator_dev *rdev, + unsigned selector) + { +@@ -1314,6 +1383,18 @@ static const struct regulator_ops spmi_s + .set_pull_down = spmi_regulator_common_set_pull_down, + }; + ++static const struct regulator_ops spmi_smps_vmpwm_ops = { ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .is_enabled = regulator_is_enabled_regmap, ++ .set_voltage = spmi_regulator_smps_vmpwm_set_vol_uV, ++ .get_voltage = spmi_regulator_smps_vmpwm_get_vol_uV, ++ .map_voltage = spmi_regulator_common_map_voltage, ++ .list_voltage = spmi_regulator_common_list_voltage, ++ .set_mode = spmi_regulator_common_set_mode, ++ .get_mode = spmi_regulator_common_get_mode, ++}; ++ + static const struct regulator_ops spmi_ldo_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, +@@ -1459,6 +1540,7 @@ static const struct regulator_ops spmi_h + + static const struct spmi_regulator_mapping supported_regulators[] = { + /* type subtype dig_min dig_max ltype ops setpoints hpm_min */ ++ SPMI_VREG(BUCK, VMPWM_CTL, 0, INF, SMPS, smps_vmpwm, smps_vmpwm, 0), + SPMI_VREG(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000), + SPMI_VREG(BUCK, HFS430, 0, INF, HFS430, hfs430, hfs430, 10000), + SPMI_VREG(LDO, N300, 0, INF, LDO, ldo, nldo1, 10000), diff --git a/target/linux/ipq807x/patches-5.15/0127-ipq807x-sdhc-Fixed-SDR104-mode-card-detection.patch b/target/linux/ipq807x/patches-5.15/0127-ipq807x-sdhc-Fixed-SDR104-mode-card-detection.patch new file mode 100644 index 000000000..65a0c6c83 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0127-ipq807x-sdhc-Fixed-SDR104-mode-card-detection.patch @@ -0,0 +1,42 @@ +From 6ac91c1597dfd688b5e818708aa4f8c55a41eefb Mon Sep 17 00:00:00 2001 +From: Vasudevan Murugesan +Date: Thu, 8 Jun 2017 19:13:48 +0530 +Subject: [PATCH] ipq807x: sdhc: Fixed SDR104 mode card detection + +Change-Id: I353356284d28d09d79bf7d318c4ebcdbc15e5b02 +Signed-off-by: Vasudevan Murugesan +Signed-off-by: Saravanan Jaganathan +(cherry picked from commit 080d3f390aa409ef2b5adf59a175b6bb2aa863fd) +Signed-off-by: Praveenkumar I + +Change-Id: Ie5edb7b3d972e06f3eb2525e10597b49e9bae14d +--- + drivers/regulator/qcom_spmi-regulator.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -165,6 +165,7 @@ enum spmi_regulator_subtype { + SPMI_REGULATOR_SUBTYPE_ULT_HF_CTL4 = 0x10, + SPMI_REGULATOR_SUBTYPE_HFS430 = 0x0a, + SPMI_REGULATOR_SUBTYPE_VMPWM_CTL = 0x0a, ++ SPMI_REGULATOR_SUBTYPE_HT_P150 = 0x35, + }; + + enum spmi_common_regulator_registers { +@@ -493,6 +494,7 @@ static struct spmi_voltage_range smps_ra + + static struct spmi_voltage_range smps_vmpwm_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 664000, 664000, 1104000, 1104000, 8000), ++ SPMI_VOLTAGE_RANGE(1, 1104000, 1104000, 3300000, 3300000, 8000), + }; + + static struct spmi_voltage_range ftsmps_ranges[] = { +@@ -1540,6 +1542,7 @@ static const struct regulator_ops spmi_h + + static const struct spmi_regulator_mapping supported_regulators[] = { + /* type subtype dig_min dig_max ltype ops setpoints hpm_min */ ++ SPMI_VREG(LDO, HT_P150, 0, INF, LDO, smps_vmpwm, smps_vmpwm, 0), + SPMI_VREG(BUCK, VMPWM_CTL, 0, INF, SMPS, smps_vmpwm, smps_vmpwm, 0), + SPMI_VREG(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000), + SPMI_VREG(BUCK, HFS430, 0, INF, HFS430, hfs430, hfs430, 10000), diff --git a/target/linux/ipq807x/patches-5.15/0128-ipq807x-spmi-regulator-Add-separate-voltage-range-fo.patch b/target/linux/ipq807x/patches-5.15/0128-ipq807x-spmi-regulator-Add-separate-voltage-range-fo.patch new file mode 100644 index 000000000..ef37bbeee --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0128-ipq807x-spmi-regulator-Add-separate-voltage-range-fo.patch @@ -0,0 +1,48 @@ +From 93ed08e80c610330f90b20415093fe388835f355 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Mon, 13 Jul 2020 18:13:48 +0530 +Subject: [PATCH] ipq807x: spmi regulator: Add separate voltage range for LDO + +When LDO voltage range added in SMPS voltage range structure, +selector value used during set voltage is wrongly calculated. +Because the SMPS voltage range is also taken into account for LDO. + +So, a separate voltage range structure is introduced for LDO. + +Signed-off-by: Praveenkumar I +Change-Id: I883518ae0686762a3774750b1dd480c4fe7298f3 +--- + drivers/regulator/qcom_spmi-regulator.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -494,7 +494,10 @@ static struct spmi_voltage_range smps_ra + + static struct spmi_voltage_range smps_vmpwm_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 664000, 664000, 1104000, 1104000, 8000), +- SPMI_VOLTAGE_RANGE(1, 1104000, 1104000, 3300000, 3300000, 8000), ++}; ++ ++static struct spmi_voltage_range ldo_vmpwm_ranges[] = { ++ SPMI_VOLTAGE_RANGE(0, 1104000, 1104000, 3300000, 3300000, 8000), + }; + + static struct spmi_voltage_range ftsmps_ranges[] = { +@@ -563,6 +566,7 @@ static DEFINE_SPMI_SET_POINTS(nldo3); + static DEFINE_SPMI_SET_POINTS(ln_ldo); + static DEFINE_SPMI_SET_POINTS(smps); + static DEFINE_SPMI_SET_POINTS(smps_vmpwm); ++static DEFINE_SPMI_SET_POINTS(ldo_vmpwm); + static DEFINE_SPMI_SET_POINTS(ftsmps); + static DEFINE_SPMI_SET_POINTS(ftsmps2p5); + static DEFINE_SPMI_SET_POINTS(ftsmps426); +@@ -1542,7 +1546,7 @@ static const struct regulator_ops spmi_h + + static const struct spmi_regulator_mapping supported_regulators[] = { + /* type subtype dig_min dig_max ltype ops setpoints hpm_min */ +- SPMI_VREG(LDO, HT_P150, 0, INF, LDO, smps_vmpwm, smps_vmpwm, 0), ++ SPMI_VREG(LDO, HT_P150, 0, INF, LDO, smps_vmpwm, ldo_vmpwm, 0), + SPMI_VREG(BUCK, VMPWM_CTL, 0, INF, SMPS, smps_vmpwm, smps_vmpwm, 0), + SPMI_VREG(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000), + SPMI_VREG(BUCK, HFS430, 0, INF, HFS430, hfs430, hfs430, 10000), diff --git a/target/linux/ipq807x/patches-5.15/0129-arm64-dts-ipq8074-add-SPMI-PMIC-regulators.patch b/target/linux/ipq807x/patches-5.15/0129-arm64-dts-ipq8074-add-SPMI-PMIC-regulators.patch new file mode 100644 index 000000000..297d77f0e --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0129-arm64-dts-ipq8074-add-SPMI-PMIC-regulators.patch @@ -0,0 +1,65 @@ +From c175f32484c75d0d3fe9c4024226c1067957d0a8 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 26 Dec 2021 19:04:26 +0100 +Subject: [PATCH] arm64: dts: ipq8074: add SPMI PMIC regulators + +PMD9655 is used in IPQ8074 and provides S3 for cores, +S4 for UBI core and LDO11 for SDIO/eMMC. + +So, lets add the nodes in preparation for DVFS later. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 34 +++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -5,6 +5,7 @@ + + #include + #include ++#include + + / { + model = "Qualcomm Technologies, Inc. IPQ8074"; +@@ -451,6 +452,39 @@ + interrupt-controller; + #interrupt-cells = <4>; + cell-index = <0>; ++ ++ pmic@1 { ++ compatible ="qcom,spmi-pmic"; ++ reg = <0x1 SPMI_USID>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ regulators { ++ compatible = "qcom,pmd9655-regulators"; ++ ++ s3: s3 { ++ regulator-name = "pmd9655_s3"; ++ regulator-min-microvolt = <592000>; ++ regulator-max-microvolt = <1064000>; ++ regulator-always-on; ++ regulator-boot-on; ++ }; ++ ++ s4: s4 { ++ regulator-name = "pmd9655_s4"; ++ regulator-min-microvolt = <712000>; ++ regulator-max-microvolt = <992000>; ++ regulator-always-on; ++ regulator-boot-on; ++ }; ++ ++ ldo11: ldo11 { ++ regulator-name = "pmd9655_ldo11"; ++ regulator-min-microvolt = <1104000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ }; ++ }; + }; + + sdhc_1: sdhci@7824900 { diff --git a/target/linux/ipq807x/patches-5.15/0130-clk-qcom-clk-alpha-pll-add-support-for-APSS-PLL.patch b/target/linux/ipq807x/patches-5.15/0130-clk-qcom-clk-alpha-pll-add-support-for-APSS-PLL.patch new file mode 100644 index 000000000..f8e02f933 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0130-clk-qcom-clk-alpha-pll-add-support-for-APSS-PLL.patch @@ -0,0 +1,47 @@ +From 5a127450125f71247b7384930459b892da227e28 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 28 Dec 2021 20:32:46 +0100 +Subject: [PATCH] clk: qcom: clk-alpha-pll: add support for APSS PLL + +APSS PLL type will be used by the IPQ8074 APSS driver for providing the +CPU core clocks and enabling CPU Frequency scaling. + +This is ported from the downstream 5.4 kernel. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/clk-alpha-pll.c | 12 ++++++++++++ + drivers/clk/qcom/clk-alpha-pll.h | 1 + + 2 files changed, 13 insertions(+) + +--- a/drivers/clk/qcom/clk-alpha-pll.c ++++ b/drivers/clk/qcom/clk-alpha-pll.c +@@ -139,6 +139,18 @@ const u8 clk_alpha_pll_regs[][PLL_OFF_MA + [PLL_OFF_OPMODE] = 0x28, + [PLL_OFF_STATUS] = 0x38, + }, ++ [CLK_ALPHA_PLL_TYPE_APSS] = { ++ [PLL_OFF_L_VAL] = 0x08, ++ [PLL_OFF_ALPHA_VAL] = 0x10, ++ [PLL_OFF_ALPHA_VAL_U] = 0xff, ++ [PLL_OFF_USER_CTL] = 0x18, ++ [PLL_OFF_USER_CTL_U] = 0xff, ++ [PLL_OFF_CONFIG_CTL] = 0x20, ++ [PLL_OFF_CONFIG_CTL_U] = 0x24, ++ [PLL_OFF_TEST_CTL] = 0x30, ++ [PLL_OFF_TEST_CTL_U] = 0x34, ++ [PLL_OFF_STATUS] = 0x28, ++ }, + }; + EXPORT_SYMBOL_GPL(clk_alpha_pll_regs); + +--- a/drivers/clk/qcom/clk-alpha-pll.h ++++ b/drivers/clk/qcom/clk-alpha-pll.h +@@ -17,6 +17,7 @@ enum { + CLK_ALPHA_PLL_TYPE_LUCID = CLK_ALPHA_PLL_TYPE_TRION, + CLK_ALPHA_PLL_TYPE_AGERA, + CLK_ALPHA_PLL_TYPE_ZONDA, ++ CLK_ALPHA_PLL_TYPE_APSS, + CLK_ALPHA_PLL_TYPE_MAX, + }; + diff --git a/target/linux/ipq807x/patches-5.15/0131-clk-qcom-Add-DT-bindings-for-IPQ8074-APSS-clock-cont.patch b/target/linux/ipq807x/patches-5.15/0131-clk-qcom-Add-DT-bindings-for-IPQ8074-APSS-clock-cont.patch new file mode 100644 index 000000000..93fa2ff27 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0131-clk-qcom-Add-DT-bindings-for-IPQ8074-APSS-clock-cont.patch @@ -0,0 +1,30 @@ +From ab17c6d31f07271b42c6c36c9ad785bdc2871e62 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 28 Dec 2021 20:36:45 +0100 +Subject: [PATCH] clk: qcom: Add DT bindings for IPQ8074 APSS clock controller + +Add DT-binding for the IPQ8074 APSS clock controller. + +Signed-off-by: Robert Marko +--- + include/dt-bindings/clock/qcom,apss-ipq8074.h | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + create mode 100644 include/dt-bindings/clock/qcom,apss-ipq8074.h + +--- /dev/null ++++ b/include/dt-bindings/clock/qcom,apss-ipq8074.h +@@ -0,0 +1,14 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Copyright (c) 2021, The Linux Foundation. All rights reserved. ++ */ ++ ++#ifndef _DT_BINDINGS_CLOCK_QCA_APSS_IPQ8074_H ++#define _DT_BINDINGS_CLOCK_QCA_APSS_IPQ8074_H ++ ++#define APSS_PLL_EARLY 0 ++#define APSS_PLL 1 ++#define APCS_ALIAS0_CLK_SRC 2 ++#define APCS_ALIAS0_CORE_CLK 3 ++ ++#endif diff --git a/target/linux/ipq807x/patches-5.15/0132-clk-qcom-Add-IPQ8074-APSS-clock-controller.patch b/target/linux/ipq807x/patches-5.15/0132-clk-qcom-Add-IPQ8074-APSS-clock-controller.patch new file mode 100644 index 000000000..3b5688591 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0132-clk-qcom-Add-IPQ8074-APSS-clock-controller.patch @@ -0,0 +1,220 @@ +From c0333749b53881e61ecdfc62f253e24b01dda129 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 28 Dec 2021 20:37:55 +0100 +Subject: [PATCH] clk: qcom: Add IPQ8074 APSS clock controller + +IPQ8074 APSS clock controller provides the clock for the IPQ8074 CPU +cores, thus also providing support for CPU frequency scaling. + +It looks like they are clocked by the XO and a custom APSS type PLL. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/Kconfig | 10 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/apss-ipq8074.c | 170 ++++++++++++++++++++++++++++++++ + 3 files changed, 181 insertions(+) + create mode 100644 drivers/clk/qcom/apss-ipq8074.c + +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -134,6 +134,16 @@ config IPQ_APSS_6018 + Say Y if you want to support CPU frequency scaling on + ipq based devices. + ++config IPQ_APSS_8074 ++ tristate "IPQ8074 APSS Clock Controller" ++ depends on QCOM_APCS_IPC || COMPILE_TEST ++ help ++ Support for APSS clock controller on IPQ8074 platforms. The ++ APSS clock controller manages the Mux and enable block that feeds the ++ CPUs. ++ Say Y if you want to support CPU frequency scaling on ++ ipq based devices. ++ + config IPQ_GCC_4019 + tristate "IPQ4019 Global Clock Controller" + help +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -22,6 +22,7 @@ obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8 + obj-$(CONFIG_CLK_GFM_LPASS_SM8250) += lpass-gfm-sm8250.o + obj-$(CONFIG_IPQ_APSS_PLL) += apss-ipq-pll.o + obj-$(CONFIG_IPQ_APSS_6018) += apss-ipq6018.o ++obj-$(CONFIG_IPQ_APSS_8074) += apss-ipq8074.o + obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o + obj-$(CONFIG_IPQ_GCC_6018) += gcc-ipq6018.o + obj-$(CONFIG_IPQ_GCC_806X) += gcc-ipq806x.o +--- /dev/null ++++ b/drivers/clk/qcom/apss-ipq8074.c +@@ -0,0 +1,170 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2021, The Linux Foundation. All rights reserved. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "common.h" ++#include "clk-regmap.h" ++#include "clk-pll.h" ++#include "clk-rcg.h" ++#include "clk-branch.h" ++#include "clk-alpha-pll.h" ++#include "clk-regmap-divider.h" ++#include "clk-regmap-mux.h" ++ ++#define F(f, s, h, m, n) { (f), (s), (2 * (h) - 1), (m), (n) } ++ ++enum { ++ P_XO, ++ P_GPLL0, ++ P_GPLL2, ++ P_GPLL4, ++ P_APSS_PLL_EARLY, ++ P_APSS_PLL ++}; ++ ++static struct clk_alpha_pll apss_pll_early = { ++ .offset = 0x5000, ++ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_APSS], ++ .clkr = { ++ .enable_reg = 0x5000, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "apss_pll_early", ++ .parent_names = (const char *[]){ ++ "xo" ++ }, ++ .num_parents = 1, ++ .ops = &clk_alpha_pll_huayra_ops, ++ }, ++ }, ++}; ++ ++static struct clk_alpha_pll_postdiv apss_pll = { ++ .offset = 0x5000, ++ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_APSS], ++ .width = 2, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "apss_pll", ++ .parent_names = (const char *[]){ "apss_pll_early" }, ++ .num_parents = 1, ++ .ops = &clk_alpha_pll_postdiv_ro_ops, ++ }, ++}; ++ ++static const char * const parents_apcs_alias0_clk_src[] = { ++ "xo", ++ "gpll0", ++ "gpll2", ++ "gpll4", ++ "apss_pll", ++ "apss_pll_early", ++}; ++ ++static const struct parent_map parents_apcs_alias0_clk_src_map[] = { ++ { P_XO, 0 }, ++ { P_GPLL0, 4 }, ++ { P_GPLL2, 2 }, ++ { P_GPLL4, 1 }, ++ { P_APSS_PLL, 3 }, ++ { P_APSS_PLL_EARLY, 5 }, ++}; ++ ++struct freq_tbl ftbl_apcs_alias0_clk_src[] = { ++ F(19200000, P_XO, 1, 0, 0), ++ F(403200000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(806400000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(1017600000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(1382400000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(1651200000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(1843200000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(1920000000, P_APSS_PLL_EARLY, 1, 0, 0), ++ F(2208000000UL, P_APSS_PLL_EARLY, 1, 0, 0), ++ { } ++}; ++ ++struct clk_rcg2 apcs_alias0_clk_src = { ++ .cmd_rcgr = 0x0050, ++ .freq_tbl = ftbl_apcs_alias0_clk_src, ++ .hid_width = 5, ++ .parent_map = parents_apcs_alias0_clk_src_map, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "apcs_alias0_clk_src", ++ .parent_names = parents_apcs_alias0_clk_src, ++ .num_parents = 6, ++ .ops = &clk_rcg2_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ ++static struct clk_branch apcs_alias0_core_clk = { ++ .halt_reg = 0x0058, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x0058, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "apcs_alias0_core_clk", ++ .parent_names = (const char *[]){ ++ "apcs_alias0_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | ++ CLK_IS_CRITICAL, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_regmap *apss_ipq8074_clks[] = { ++ [APSS_PLL_EARLY] = &apss_pll_early.clkr, ++ [APSS_PLL] = &apss_pll.clkr, ++ [APCS_ALIAS0_CLK_SRC] = &apcs_alias0_clk_src.clkr, ++ [APCS_ALIAS0_CORE_CLK] = &apcs_alias0_core_clk.clkr, ++}; ++ ++static const struct regmap_config apss_ipq8074_regmap_config = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x5ffc, ++ .fast_io = true, ++}; ++ ++static const struct qcom_cc_desc apss_ipq8074_desc = { ++ .config = &apss_ipq8074_regmap_config, ++ .clks = apss_ipq8074_clks, ++ .num_clks = ARRAY_SIZE(apss_ipq8074_clks), ++}; ++ ++static int apss_ipq8074_probe(struct platform_device *pdev) ++{ ++ struct regmap *regmap; ++ ++ regmap = dev_get_regmap(pdev->dev.parent, NULL); ++ if (!regmap) ++ return -ENODEV; ++ ++ return qcom_cc_really_probe(pdev, &apss_ipq8074_desc, regmap); ++} ++ ++static struct platform_driver apss_ipq8074_driver = { ++ .probe = apss_ipq8074_probe, ++ .driver = { ++ .name = "qcom,apss-ipq8074-clk", ++ }, ++}; ++ ++module_platform_driver(apss_ipq8074_driver); ++ ++MODULE_DESCRIPTION("Qualcomm IPQ8074 APSS clock driver"); ++MODULE_LICENSE("GPLv2"); diff --git a/target/linux/ipq807x/patches-5.15/0133-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-control.patch b/target/linux/ipq807x/patches-5.15/0133-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-control.patch new file mode 100644 index 000000000..8aaf3032c --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0133-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-control.patch @@ -0,0 +1,39 @@ +From cef0d7940ff741590c638ced909cb9e58b9d8bb0 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 28 Dec 2021 20:59:18 +0100 +Subject: [PATCH] mailbox: qcom-apcs-ipc: add IPQ8074 APSS clock controller + support + +IPQ8074 has the APSS clock controller utilizing the same register space as +the APCS, so provide acess to the APSS utilizing a child device like +IPQ6018 does as well, but just by utilizing the IPQ8074 specific APSS +clock driver. + +Also, APCS register space in IPQ8074 is 0x6000 so max_register needs to be +updated to 0x5FFC. + +Signed-off-by: Robert Marko +--- + drivers/mailbox/qcom-apcs-ipc-mailbox.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/mailbox/qcom-apcs-ipc-mailbox.c ++++ b/drivers/mailbox/qcom-apcs-ipc-mailbox.c +@@ -34,7 +34,7 @@ static const struct qcom_apcs_ipc_data i + }; + + static const struct qcom_apcs_ipc_data ipq8074_apcs_data = { +- .offset = 8, .clk_name = NULL ++ .offset = 8, .clk_name = "qcom,apss-ipq8074-clk" + }; + + static const struct qcom_apcs_ipc_data msm8916_apcs_data = { +@@ -73,7 +73,7 @@ static const struct regmap_config apcs_r + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +- .max_register = 0x1008, ++ .max_register = 0x5FFC, + .fast_io = true, + }; + diff --git a/target/linux/ipq807x/patches-5.15/0134-arm64-dts-ipq8074-update-APCS-node-due-to-clock-supp.patch b/target/linux/ipq807x/patches-5.15/0134-arm64-dts-ipq8074-update-APCS-node-due-to-clock-supp.patch new file mode 100644 index 000000000..64e89de82 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0134-arm64-dts-ipq8074-update-APCS-node-due-to-clock-supp.patch @@ -0,0 +1,28 @@ +From 89b34e0f57eaa18fc04ff038372c8d1facf55fa8 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 28 Dec 2021 21:07:17 +0100 +Subject: [PATCH] arm64: dts: ipq8074: update APCS node due to clock support + +APCS now has support for providing the APSS clocks as the child device +for IPQ8074, so update the DT node to reflect the expanded register space +as well as add #clock-cells property as it now provides the APSS clock +that will be used for CPU scaling. + +Signed-off-by: Robert Marko +--- + 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 +@@ -971,8 +971,9 @@ + + apcs_glb: mailbox@b111000 { + compatible = "qcom,ipq8074-apcs-apps-global"; +- reg = <0x0b111000 0x1000>; ++ reg = <0x0b111000 0x6000>; + ++ #clock-cells = <1>; + #mbox-cells = <1>; + }; + diff --git a/target/linux/ipq807x/patches-5.15/0135-arm64-dts-ipq8074-add-CPU-clock-and-regulator.patch b/target/linux/ipq807x/patches-5.15/0135-arm64-dts-ipq8074-add-CPU-clock-and-regulator.patch new file mode 100644 index 000000000..e0525cc8c --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0135-arm64-dts-ipq8074-add-CPU-clock-and-regulator.patch @@ -0,0 +1,66 @@ +From c8cda381dfd1fd083d6d2f56f71d33144c042a43 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 31 Dec 2021 17:56:14 +0100 +Subject: [PATCH] arm64: dts: ipq8074: add CPU clock and regulator + +Now that we have drivers for both the CPU voltage regulator and clock +controller, add the required DT properties to CPU cores. + +OPP tables are not added as they are different for the IPQ8072/4/6/8 and +IPQ8070/1 SoC-s + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -4,6 +4,7 @@ + */ + + #include ++#include + #include + #include + +@@ -35,6 +36,9 @@ + reg = <0x0>; + next-level-cache = <&L2_0>; + enable-method = "psci"; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; ++ cpu-supply = <&s3>; + }; + + CPU1: cpu@1 { +@@ -43,6 +47,9 @@ + enable-method = "psci"; + reg = <0x1>; + next-level-cache = <&L2_0>; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; ++ cpu-supply = <&s3>; + }; + + CPU2: cpu@2 { +@@ -51,6 +58,9 @@ + enable-method = "psci"; + reg = <0x2>; + next-level-cache = <&L2_0>; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; ++ cpu-supply = <&s3>; + }; + + CPU3: cpu@3 { +@@ -59,6 +69,9 @@ + enable-method = "psci"; + reg = <0x3>; + next-level-cache = <&L2_0>; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; ++ cpu-supply = <&s3>; + }; + + L2_0: l2-cache { diff --git a/target/linux/ipq807x/patches-5.15/0136-arm64-dts-ipq8074-add-label-to-cpus.patch b/target/linux/ipq807x/patches-5.15/0136-arm64-dts-ipq8074-add-label-to-cpus.patch new file mode 100644 index 000000000..9f217405b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0136-arm64-dts-ipq8074-add-label-to-cpus.patch @@ -0,0 +1,25 @@ +From 9fb45b1b02930be459d5722250c84532ce53b787 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 31 Dec 2021 18:42:53 +0100 +Subject: [PATCH] arm64: dts: ipq8074: add label to cpus + +Add label to cpus node as that makes it easy to add OPP table in SoC model +specific DTSI as IPQ8074 family has differing clocks and voltages based on +the specific model. + +Signed-off-by: Robert Marko +--- + 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 +@@ -26,7 +26,7 @@ + }; + }; + +- cpus { ++ cpus: cpus { + #address-cells = <0x1>; + #size-cells = <0x0>; + diff --git a/target/linux/ipq807x/patches-5.15/0137-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch b/target/linux/ipq807x/patches-5.15/0137-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch new file mode 100644 index 000000000..8bea2155d --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0137-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch @@ -0,0 +1,48 @@ +From 26fe80d67ab1351faa2e745a20152de71d38124f Mon Sep 17 00:00:00 2001 +From: Robert Marko +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 +--- + 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 +@@ -39,6 +39,7 @@ + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; + cpu-supply = <&s3>; ++ #cooling-cells = <2>; + }; + + CPU1: cpu@1 { +@@ -50,6 +51,7 @@ + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; + cpu-supply = <&s3>; ++ #cooling-cells = <2>; + }; + + CPU2: cpu@2 { +@@ -61,6 +63,7 @@ + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; + cpu-supply = <&s3>; ++ #cooling-cells = <2>; + }; + + CPU3: cpu@3 { +@@ -72,6 +75,7 @@ + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; + cpu-supply = <&s3>; ++ #cooling-cells = <2>; + }; + + L2_0: l2-cache { diff --git a/target/linux/ipq807x/patches-5.15/0138-clk-ipq-support-for-resetting-multiple-bits.patch b/target/linux/ipq807x/patches-5.15/0138-clk-ipq-support-for-resetting-multiple-bits.patch new file mode 100644 index 000000000..ccf09376a --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0138-clk-ipq-support-for-resetting-multiple-bits.patch @@ -0,0 +1,56 @@ +From 2fc17ac5ce7a8c6c7564c4b91e06f2cde62d58be Mon Sep 17 00:00:00 2001 +From: Rajkumar Ayyasamy +Date: Wed, 18 Mar 2020 17:08:11 +0530 +Subject: [PATCH] clk: ipq: support for resetting multiple bits + +Current reset structure takes only one reset bit and +calculates the bitmask in its reset operation. Some of the +reset registers contains multiple bits in which each bit +will be associated with subsystem reset inside the block. To +reset properly the complete block, all the subsystem reset +should be triggered at same time i.e the register write +should go in one AHB write. + +This patch adds the support for giving the complete bitmask +in reset structure and reset operation will use this bitmask +for all reset operations. + +Change-Id: Ief49f8746624a0fc1e067d815725ae7c254c2c6f +Signed-off-by: Abhishek Sahu +(cherry picked from commit ef555fc1cffa6e823a9d929711cacae0821b35ec) +Signed-off-by: Rajkumar Ayyasamy +--- + drivers/clk/qcom/reset.c | 4 ++-- + drivers/clk/qcom/reset.h | 1 + + 2 files changed, 3 insertions(+), 2 deletions(-) + +--- a/drivers/clk/qcom/reset.c ++++ b/drivers/clk/qcom/reset.c +@@ -28,7 +28,7 @@ qcom_reset_assert(struct reset_controlle + + rst = to_qcom_reset_controller(rcdev); + map = &rst->reset_map[id]; +- mask = BIT(map->bit); ++ mask = map->bitmask ? map->bitmask : BIT(map->bit); + + return regmap_update_bits(rst->regmap, map->reg, mask, mask); + } +@@ -42,7 +42,7 @@ qcom_reset_deassert(struct reset_control + + rst = to_qcom_reset_controller(rcdev); + map = &rst->reset_map[id]; +- mask = BIT(map->bit); ++ mask = map->bitmask ? map->bitmask : BIT(map->bit); + + return regmap_update_bits(rst->regmap, map->reg, mask, 0); + } +--- a/drivers/clk/qcom/reset.h ++++ b/drivers/clk/qcom/reset.h +@@ -11,6 +11,7 @@ + struct qcom_reset_map { + unsigned int reg; + u8 bit; ++ u32 bitmask; + }; + + struct regmap; diff --git a/target/linux/ipq807x/patches-5.15/0139-clk-qcom-ipq8074-add-missing-networking-resets.patch b/target/linux/ipq807x/patches-5.15/0139-clk-qcom-ipq8074-add-missing-networking-resets.patch new file mode 100644 index 000000000..52a45ea9e --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0139-clk-qcom-ipq8074-add-missing-networking-resets.patch @@ -0,0 +1,62 @@ +From 0981de6ff0a072fd25d919e661ac22890a7a1e34 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 1 Jan 2022 18:15:03 +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 +--- + drivers/clk/qcom/gcc-ipq8074.c | 14 ++++++++++++++ + include/dt-bindings/clock/qcom,gcc-ipq8074.h | 14 ++++++++++++++ + 2 files changed, 28 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4745,6 +4745,20 @@ static const struct qcom_reset_map gcc_i + [GCC_PCIE1_AHB_ARES] = { 0x76040, 5 }, + [GCC_PCIE1_AXI_MASTER_STICKY_ARES] = { 0x76040, 6 }, + [GCC_WCSSAON_RESET] = { 0x59010, 0 }, ++ [GCC_PPE_FULL_RESET] = { 0x68014, 0, GENMASK(19, 16) }, ++ [GCC_UNIPHY0_SOFT_RESET] = { 0x56004, 0, GENMASK(13, 4) | BIT(1) }, ++ [GCC_UNIPHY0_XPCS_RESET] = { 0x56004, 2 }, ++ [GCC_UNIPHY1_SOFT_RESET] = { 0x56104, 0, GENMASK(5, 4) | BIT(1) }, ++ [GCC_UNIPHY1_XPCS_RESET] = { 0x56104, 2 }, ++ [GCC_UNIPHY2_SOFT_RESET] = { 0x56204, 0, GENMASK(5, 4) | BIT(1) }, ++ [GCC_UNIPHY2_XPCS_RESET] = { 0x56204, 2 }, ++ [GCC_EDMA_HW_RESET] = { 0x68014, 0, GENMASK(21, 20) }, ++ [GCC_NSSPORT1_RESET] = { 0x68014, 0, BIT(24) | GENMASK(1, 0) }, ++ [GCC_NSSPORT2_RESET] = { 0x68014, 0, BIT(25) | GENMASK(3, 2) }, ++ [GCC_NSSPORT3_RESET] = { 0x68014, 0, BIT(26) | GENMASK(5, 4) }, ++ [GCC_NSSPORT4_RESET] = { 0x68014, 0, BIT(27) | GENMASK(9, 8) }, ++ [GCC_NSSPORT5_RESET] = { 0x68014, 0, BIT(28) | GENMASK(11, 10) }, ++ [GCC_NSSPORT6_RESET] = { 0x68014, 0, BIT(29) | GENMASK(13, 12) }, + }; + + static const struct of_device_id gcc_ipq8074_match_table[] = { +--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +@@ -367,5 +367,19 @@ + #define GCC_PCIE1_AXI_MASTER_STICKY_ARES 130 + #define GCC_PCIE0_AXI_SLAVE_STICKY_ARES 131 + #define GCC_WCSSAON_RESET 132 ++#define GCC_PPE_FULL_RESET 133 ++#define GCC_UNIPHY0_SOFT_RESET 134 ++#define GCC_UNIPHY0_XPCS_RESET 135 ++#define GCC_UNIPHY1_SOFT_RESET 136 ++#define GCC_UNIPHY1_XPCS_RESET 137 ++#define GCC_UNIPHY2_SOFT_RESET 138 ++#define GCC_UNIPHY2_XPCS_RESET 139 ++#define GCC_EDMA_HW_RESET 140 ++#define GCC_NSSPORT1_RESET 141 ++#define GCC_NSSPORT2_RESET 142 ++#define GCC_NSSPORT3_RESET 143 ++#define GCC_NSSPORT4_RESET 144 ++#define GCC_NSSPORT5_RESET 145 ++#define GCC_NSSPORT6_RESET 146 + + #endif diff --git a/target/linux/ipq807x/patches-5.15/0140-clk-qcom-ipq8074-fix-NSS-core-PLL-s.patch b/target/linux/ipq807x/patches-5.15/0140-clk-qcom-ipq8074-fix-NSS-core-PLL-s.patch new file mode 100644 index 000000000..44493bf90 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0140-clk-qcom-ipq8074-fix-NSS-core-PLL-s.patch @@ -0,0 +1,80 @@ +From 73344249026d524544e2f86c737759737c962e28 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 1 Jan 2022 18:48:56 +0100 +Subject: [PATCH] clk: qcom: ipq8074: fix NSS core PLL-s +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Like in IPQ6018 the NSS related Alpha PLL-s require initial configuration +to work. + +So, obtain the regmap that is required for the Alpha PLL configuration +and thus utilize the qcom_cc_really_probe() as we already have the regmap. +Then utilize the Alpha PLL configs from the downstream QCA 5.4 based +kernel to configure them. + +This fixes the UBI32 and NSS crypto PLL-s failing to get enabled by the +kernel. + +Fixes: b8e7e519625f ("clk: qcom: ipq8074: add remaining PLL’s") +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/gcc-ipq8074.c | 39 +++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4371,6 +4371,33 @@ static struct clk_branch gcc_pcie0_axi_s + }, + }; + ++static const struct alpha_pll_config ubi32_pll_config = { ++ .l = 0x4e, ++ .config_ctl_val = 0x200d4aa8, ++ .config_ctl_hi_val = 0x3c2, ++ .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), ++}; ++ ++static const struct alpha_pll_config nss_crypto_pll_config = { ++ .l = 0x3e, ++ .alpha = 0x0, ++ .alpha_hi = 0x80, ++ .config_ctl_val = 0x4001055b, ++ .main_output_mask = BIT(0), ++ .pre_div_val = 0x0, ++ .pre_div_mask = GENMASK(14, 12), ++ .post_div_val = 0x1 << 8, ++ .post_div_mask = GENMASK(11, 8), ++ .vco_mask = GENMASK(21, 20), ++ .vco_val = 0x0, ++ .alpha_en_mask = BIT(24), ++}; ++ + static struct clk_hw *gcc_ipq8074_hws[] = { + &gpll0_out_main_div2.hw, + &gpll6_out_main_div2.hw, +@@ -4787,7 +4814,17 @@ static const struct qcom_cc_desc gcc_ipq + + static int gcc_ipq8074_probe(struct platform_device *pdev) + { +- return qcom_cc_probe(pdev, &gcc_ipq8074_desc); ++ struct regmap *regmap; ++ ++ regmap = qcom_cc_map(pdev, &gcc_ipq8074_desc); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ clk_alpha_pll_configure(&ubi32_pll_main, regmap, &ubi32_pll_config); ++ clk_alpha_pll_configure(&nss_crypto_pll_main, regmap, ++ &nss_crypto_pll_config); ++ ++ return qcom_cc_really_probe(pdev, &gcc_ipq8074_desc, regmap); + } + + static struct platform_driver gcc_ipq8074_driver = { diff --git a/target/linux/ipq807x/patches-5.15/0141-clk-qcom-ipq8074-disable-USB-GDSC-s-SW_COLLAPSE.patch b/target/linux/ipq807x/patches-5.15/0141-clk-qcom-ipq8074-disable-USB-GDSC-s-SW_COLLAPSE.patch new file mode 100644 index 000000000..1b9715829 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0141-clk-qcom-ipq8074-disable-USB-GDSC-s-SW_COLLAPSE.patch @@ -0,0 +1,30 @@ +From 1a33a943c643b43033af936f297898b540361c62 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 1 Jan 2022 19:11:55 +0100 +Subject: [PATCH] clk: qcom: ipq8074: disable USB GDSC-s SW_COLLAPSE + +Like in IPQ6018 Qualcomm intentionally disables the SW_COLLAPSE on the USB +GDSC-s. + +This could potentially be better handled by utilizing the GDSC driver, but +I am not familiar with it nor do I have datasheets. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/gcc-ipq8074.c | 5 +++++ + 1 file changed, 5 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4820,6 +4820,11 @@ static int gcc_ipq8074_probe(struct plat + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + ++ /* Disable SW_COLLAPSE for USB0 GDSCR */ ++ regmap_update_bits(regmap, 0x3e078, BIT(0), 0x0); ++ /* Disable SW_COLLAPSE for USB1 GDSCR */ ++ regmap_update_bits(regmap, 0x3f078, BIT(0), 0x0); ++ + clk_alpha_pll_configure(&ubi32_pll_main, regmap, &ubi32_pll_config); + clk_alpha_pll_configure(&nss_crypto_pll_main, regmap, + &nss_crypto_pll_config); diff --git a/target/linux/ipq807x/patches-5.15/0142-clk-qcom-ipq8074-SW-workaround-for-UBI32-PLL-lock.patch b/target/linux/ipq807x/patches-5.15/0142-clk-qcom-ipq8074-SW-workaround-for-UBI32-PLL-lock.patch new file mode 100644 index 000000000..ceecff73a --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0142-clk-qcom-ipq8074-SW-workaround-for-UBI32-PLL-lock.patch @@ -0,0 +1,31 @@ +From 124d46f0397daf0bc13270ee43cc7d8166170f04 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 1 Jan 2022 19:14:59 +0100 +Subject: [PATCH] clk: qcom: ipq8074: SW workaround for UBI32 PLL lock + +UBI32 Huayra PLL fails to lock in 5 us in some SoC silicon and thus it +will cause the wait_for_pll() to timeout and thus return the error +indicating that the PLL failed to lock. + +This is bug in Huayra PLL HW for which SW workaround +is to set bit 26 of TEST_CTL register. + +This is ported from the QCA 5.4 based downstream kernel. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/gcc-ipq8074.c | 3 +++ + 1 file changed, 3 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4825,6 +4825,9 @@ static int gcc_ipq8074_probe(struct plat + /* Disable SW_COLLAPSE for USB1 GDSCR */ + regmap_update_bits(regmap, 0x3f078, BIT(0), 0x0); + ++ /* SW Workaround for UBI32 Huayra PLL */ ++ regmap_update_bits(regmap, 0x2501c, BIT(26), BIT(26)); ++ + clk_alpha_pll_configure(&ubi32_pll_main, regmap, &ubi32_pll_config); + clk_alpha_pll_configure(&nss_crypto_pll_main, regmap, + &nss_crypto_pll_config); diff --git a/target/linux/ipq807x/patches-5.15/0143-clk-qcom-ipq8074-fix-NSS-port-frequency-tables.patch b/target/linux/ipq807x/patches-5.15/0143-clk-qcom-ipq8074-fix-NSS-port-frequency-tables.patch new file mode 100644 index 000000000..abd61df0b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0143-clk-qcom-ipq8074-fix-NSS-port-frequency-tables.patch @@ -0,0 +1,64 @@ +From 7cb389923931167cc772f36fabe5f140abb28053 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 1 Jan 2022 19:29:48 +0100 +Subject: [PATCH] clk: qcom: ipq8074: fix NSS port frequency tables + +NSS port 5 and 6 frequency tables are currently broken and are causing a +wide ranges of issue like 1G not working at all on port 6 or port 5 being +clocked with 312 instead of 125 MHz as UNIPHY1 gets selected. + +So, update the frequency tables with the ones from the downstream QCA 5.4 +based kernel which has already fixed this. + +Fixes: 7117a51ed303 ("clk: qcom: ipq8074: add NSS ethernet port clocks") +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/gcc-ipq8074.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -1788,8 +1788,10 @@ static struct clk_regmap_div nss_port4_t + 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), + 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), + F(156250000, P_UNIPHY1_RX, 2, 0, 0), + F(312500000, P_UNIPHY1_RX, 1, 0, 0), + { } +@@ -1828,8 +1830,10 @@ static struct clk_regmap_div nss_port5_r + 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), + 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), + F(156250000, P_UNIPHY1_TX, 2, 0, 0), + F(312500000, P_UNIPHY1_TX, 1, 0, 0), + { } +@@ -1867,8 +1871,10 @@ static struct clk_regmap_div nss_port5_t + + 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), + 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), + F(156250000, P_UNIPHY2_RX, 2, 0, 0), + F(312500000, P_UNIPHY2_RX, 1, 0, 0), +@@ -1907,8 +1913,10 @@ static struct clk_regmap_div nss_port6_r + + 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), + 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), + F(156250000, P_UNIPHY2_TX, 2, 0, 0), + F(312500000, P_UNIPHY2_TX, 1, 0, 0), diff --git a/target/linux/ipq807x/patches-5.15/0145-clk-qcom-ipq8074-add-PPE-crypto-clock.patch b/target/linux/ipq807x/patches-5.15/0145-clk-qcom-ipq8074-add-PPE-crypto-clock.patch new file mode 100644 index 000000000..75780f983 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0145-clk-qcom-ipq8074-add-PPE-crypto-clock.patch @@ -0,0 +1,61 @@ +From f05295ef5e58a042f3a66490f6e75c6af83a329f Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 13 Mar 2022 12:46:28 +0100 +Subject: [PATCH] clk: qcom: ipq8074: add PPE crypto clock + +The built-in PPE engine has a dedicated clock for the EIP-197 crypto +engine. + +So, since the required clock currently missing add support for it. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/gcc-ipq8074.c | 19 +++++++++++++++++++ + include/dt-bindings/clock/qcom,gcc-ipq8074.h | 1 + + 2 files changed, 20 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -3182,6 +3182,24 @@ static struct clk_branch gcc_nss_ptp_ref + }, + }; + ++static struct clk_branch gcc_crypto_ppe_clk = { ++ .halt_reg = 0x68310, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x68310, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_crypto_ppe_clk", ++ .parent_names = (const char *[]){ ++ "nss_ppe_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ + static struct clk_branch gcc_nssnoc_ce_apb_clk = { + .halt_reg = 0x6830c, + .clkr = { +@@ -4644,6 +4662,7 @@ static struct clk_regmap *gcc_ipq8074_cl + [GCC_PCIE0_RCHNG_CLK_SRC] = &pcie0_rchng_clk_src.clkr, + [GCC_PCIE0_RCHNG_CLK] = &gcc_pcie0_rchng_clk.clkr, + [GCC_PCIE0_AXI_S_BRIDGE_CLK] = &gcc_pcie0_axi_s_bridge_clk.clkr, ++ [GCC_CRYPTO_PPE_CLK] = &gcc_crypto_ppe_clk.clkr, + }; + + static const struct qcom_reset_map gcc_ipq8074_resets[] = { +--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +@@ -233,6 +233,7 @@ + #define GCC_PCIE0_AXI_S_BRIDGE_CLK 224 + #define GCC_PCIE0_RCHNG_CLK_SRC 225 + #define GCC_PCIE0_RCHNG_CLK 226 ++#define GCC_CRYPTO_PPE_CLK 227 + + #define GCC_BLSP1_BCR 0 + #define GCC_BLSP1_QUP1_BCR 1 diff --git a/target/linux/ipq807x/patches-5.15/0146-clk-qcom-ipq8074-set-BRANCH_HALT_DELAY-flag-for-UBI-.patch b/target/linux/ipq807x/patches-5.15/0146-clk-qcom-ipq8074-set-BRANCH_HALT_DELAY-flag-for-UBI-.patch new file mode 100644 index 000000000..b9dc3b9aa --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0146-clk-qcom-ipq8074-set-BRANCH_HALT_DELAY-flag-for-UBI-.patch @@ -0,0 +1,100 @@ +From 28a3cea6641607c7fd717516c38351d891d3e5cb Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 13 Mar 2022 13:01:55 +0100 +Subject: [PATCH] clk: qcom: ipq8074: set BRANCH_HALT_DELAY flag for UBI clocks + +Currently, attempting to enable the UBI clocks will cause the stuck at +off warning to be printed and clk_enable will fail. + +[ 14.936694] gcc_ubi1_ahb_clk status stuck at 'off' + +Downstream 5.4 QCA kernel has fixed this by seting the BRANCH_HALT_DELAY +flag on UBI clocks, so lets do the same. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/gcc-ipq8074.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -3372,6 +3372,7 @@ static struct clk_branch gcc_nssnoc_ubi1 + + static struct clk_branch gcc_ubi0_ahb_clk = { + .halt_reg = 0x6820c, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x6820c, + .enable_mask = BIT(0), +@@ -3389,6 +3390,7 @@ static struct clk_branch gcc_ubi0_ahb_cl + + static struct clk_branch gcc_ubi0_axi_clk = { + .halt_reg = 0x68200, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68200, + .enable_mask = BIT(0), +@@ -3406,6 +3408,7 @@ static struct clk_branch gcc_ubi0_axi_cl + + static struct clk_branch gcc_ubi0_nc_axi_clk = { + .halt_reg = 0x68204, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68204, + .enable_mask = BIT(0), +@@ -3423,6 +3426,7 @@ static struct clk_branch gcc_ubi0_nc_axi + + static struct clk_branch gcc_ubi0_core_clk = { + .halt_reg = 0x68210, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68210, + .enable_mask = BIT(0), +@@ -3440,6 +3444,7 @@ static struct clk_branch gcc_ubi0_core_c + + static struct clk_branch gcc_ubi0_mpt_clk = { + .halt_reg = 0x68208, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68208, + .enable_mask = BIT(0), +@@ -3457,6 +3462,7 @@ static struct clk_branch gcc_ubi0_mpt_cl + + static struct clk_branch gcc_ubi1_ahb_clk = { + .halt_reg = 0x6822c, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x6822c, + .enable_mask = BIT(0), +@@ -3474,6 +3480,7 @@ static struct clk_branch gcc_ubi1_ahb_cl + + static struct clk_branch gcc_ubi1_axi_clk = { + .halt_reg = 0x68220, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68220, + .enable_mask = BIT(0), +@@ -3491,6 +3498,7 @@ static struct clk_branch gcc_ubi1_axi_cl + + static struct clk_branch gcc_ubi1_nc_axi_clk = { + .halt_reg = 0x68224, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68224, + .enable_mask = BIT(0), +@@ -3508,6 +3516,7 @@ static struct clk_branch gcc_ubi1_nc_axi + + static struct clk_branch gcc_ubi1_core_clk = { + .halt_reg = 0x68230, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68230, + .enable_mask = BIT(0), +@@ -3525,6 +3534,7 @@ static struct clk_branch gcc_ubi1_core_c + + static struct clk_branch gcc_ubi1_mpt_clk = { + .halt_reg = 0x68228, ++ .halt_check = BRANCH_HALT_DELAY, + .clkr = { + .enable_reg = 0x68228, + .enable_mask = BIT(0), diff --git a/target/linux/ipq807x/patches-5.15/0151-clk-ipq8074-Support-added-for-necessary-clocks-and-r.patch b/target/linux/ipq807x/patches-5.15/0151-clk-ipq8074-Support-added-for-necessary-clocks-and-r.patch new file mode 100644 index 000000000..a83207878 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0151-clk-ipq8074-Support-added-for-necessary-clocks-and-r.patch @@ -0,0 +1,311 @@ +From 6504bc9edeb1a2a54d813f4bb5d0267e7bf827f9 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Thu, 6 Feb 2020 17:35:42 +0530 +Subject: [PATCH 4/8] clk: ipq8074: Support added for necessary clocks and + reset + +Change-Id: I21a76a44185f766e9b6dcba274392ea8e599718b +Signed-off-by: Praveenkumar I +Signed-off-by: Rajkumar Ayyasamy +--- + drivers/clk/qcom/gcc-ipq8074.c | 238 ++++++++++++++++++- + include/dt-bindings/clock/qcom,gcc-ipq8074.h | 35 ++- + 2 files changed, 258 insertions(+), 15 deletions(-) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -391,6 +391,22 @@ static const struct parent_map gcc_xo_gp + { P_SLEEP_CLK, 6 }, + }; + ++static const char * const gcc_xo_gpll4_gpll0_gpll6_gpll0_div2[] = { ++ "xo", ++ "gpll4", ++ "gpll0", ++ "gpll6", ++ "gpll0_out_main_div2", ++}; ++ ++static const struct parent_map gcc_xo_gpll4_gpll0_gpll6_gpll0_div2_map[] = { ++ { P_XO, 0 }, ++ { P_GPLL4, 1 }, ++ { P_GPLL0, 2 }, ++ { P_GPLL6, 3 }, ++ { P_GPLL0_DIV2, 4 }, ++}; ++ + static struct clk_alpha_pll gpll0_main = { + .offset = 0x21000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], +@@ -962,6 +978,12 @@ static const struct freq_tbl ftbl_pcie_a + { } + }; + ++struct freq_tbl ftbl_pcie_rchng_clk_src[] = { ++ F(19200000, P_XO, 1, 0, 0), ++ F(100000000, P_GPLL0, 8, 0, 0), ++ { } ++}; ++ + static struct clk_rcg2 pcie0_axi_clk_src = { + .cmd_rcgr = 0x75054, + .freq_tbl = ftbl_pcie_axi_clk_src, +@@ -2021,6 +2043,78 @@ static struct clk_rcg2 gp3_clk_src = { + }, + }; + ++struct freq_tbl ftbl_qdss_tsctr_clk_src[] = { ++ F(160000000, P_GPLL0_DIV2, 2.5, 0, 0), ++ F(320000000, P_GPLL0, 2.5, 0, 0), ++ F(600000000, P_GPLL6, 2, 0, 0), ++ { } ++}; ++ ++struct clk_rcg2 qdss_tsctr_clk_src = { ++ .cmd_rcgr = 0x29064, ++ .freq_tbl = ftbl_qdss_tsctr_clk_src, ++ .hid_width = 5, ++ .parent_map = gcc_xo_gpll4_gpll0_gpll6_gpll0_div2_map, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "qdss_tsctr_clk_src", ++ .parent_names = gcc_xo_gpll4_gpll0_gpll6_gpll0_div2, ++ .num_parents = 5, ++ .ops = &clk_rcg2_ops, ++ }, ++}; ++ ++static struct clk_fixed_factor qdss_dap_sync_clk_src = { ++ .mult = 1, ++ .div = 4, ++ .hw.init = &(struct clk_init_data){ ++ .name = "qdss_dap_sync_clk_src", ++ .parent_names = (const char *[]){ ++ "qdss_tsctr_clk_src" ++ }, ++ .num_parents = 1, ++ .ops = &clk_fixed_factor_ops, ++ }, ++}; ++ ++struct freq_tbl ftbl_qdss_at_clk_src[] = { ++ F(66670000, P_GPLL0_DIV2, 6, 0, 0), ++ F(240000000, P_GPLL6, 6, 0, 0), ++ { } ++}; ++ ++struct clk_rcg2 qdss_at_clk_src = { ++ .cmd_rcgr = 0x2900c, ++ .freq_tbl = ftbl_qdss_at_clk_src, ++ .hid_width = 5, ++ .parent_map = gcc_xo_gpll4_gpll0_gpll6_gpll0_div2_map, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "qdss_at_clk_src", ++ .parent_names = gcc_xo_gpll4_gpll0_gpll6_gpll0_div2, ++ .num_parents = 5, ++ .ops = &clk_rcg2_ops, ++ }, ++}; ++ ++ ++struct freq_tbl ftbl_adss_pwm_clk_src[] = { ++ F(19200000, P_XO, 1, 0, 0), ++ F(200000000, P_GPLL0, 4, 0, 0), ++ { } ++}; ++ ++struct clk_rcg2 adss_pwm_clk_src = { ++ .cmd_rcgr = 0x1c008, ++ .freq_tbl = ftbl_adss_pwm_clk_src, ++ .hid_width = 5, ++ .parent_map = gcc_xo_gpll0_map, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "adss_pwm_clk_src", ++ .parent_data = gcc_xo_gpll0, ++ .num_parents = 2, ++ .ops = &clk_rcg2_ops, ++ }, ++}; ++ + static struct clk_branch gcc_blsp1_ahb_clk = { + .halt_reg = 0x01008, + .clkr = { +@@ -4352,13 +4446,7 @@ static struct clk_branch gcc_gp3_clk = { + }, + }; + +-static const struct freq_tbl ftbl_pcie_rchng_clk_src[] = { +- F(19200000, P_XO, 1, 0, 0), +- F(100000000, P_GPLL0, 8, 0, 0), +- { } +-}; +- +-static struct clk_rcg2 pcie0_rchng_clk_src = { ++struct clk_rcg2 pcie0_rchng_clk_src = { + .cmd_rcgr = 0x75070, + .freq_tbl = ftbl_pcie_rchng_clk_src, + .hid_width = 5, +@@ -4434,6 +4522,114 @@ static const struct alpha_pll_config nss + .alpha_en_mask = BIT(24), + }; + ++static struct clk_branch gcc_snoc_bus_timeout2_ahb_clk = { ++ .halt_reg = 0x4700c, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x4700c, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_snoc_bus_timeout2_ahb_clk", ++ .parent_names = (const char *[]){ ++ "usb0_master_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_snoc_bus_timeout3_ahb_clk = { ++ .halt_reg = 0x47014, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x47014, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_snoc_bus_timeout3_ahb_clk", ++ .parent_names = (const char *[]){ ++ "usb1_master_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_dcc_clk = { ++ .halt_reg = 0x77004, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x77004, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_dcc_clk", ++ .parent_names = (const char *[]){ ++ "pcnoc_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_qdss_at_clk = { ++ .halt_reg = 0x29024, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x29024, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_qdss_at_clk", ++ .parent_names = (const char *[]){ ++ "qdss_at_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_qdss_dap_clk = { ++ .halt_reg = 0x29084, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x29084, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_qdss_dap_clk", ++ .parent_names = (const char *[]){ ++ "qdss_dap_sync_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_adss_pwm_clk = { ++ .halt_reg = 0x1c020, ++ .halt_bit = 31, ++ .clkr = { ++ .enable_reg = 0x1c020, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_adss_pwm_clk", ++ .parent_names = (const char *[]){ ++ "adss_pwm_clk_src" ++ }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ + static struct clk_hw *gcc_ipq8074_hws[] = { + &gpll0_out_main_div2.hw, + &gpll6_out_main_div2.hw, +@@ -4442,6 +4638,7 @@ static struct clk_hw *gcc_ipq8074_hws[] + &gcc_xo_div4_clk_src.hw, + &nss_noc_clk_src.hw, + &nss_ppe_cdiv_clk_src.hw, ++ &qdss_dap_sync_clk_src.hw, + }; + + static struct clk_regmap *gcc_ipq8074_clks[] = { +@@ -4673,6 +4870,15 @@ static struct clk_regmap *gcc_ipq8074_cl + [GCC_PCIE0_RCHNG_CLK] = &gcc_pcie0_rchng_clk.clkr, + [GCC_PCIE0_AXI_S_BRIDGE_CLK] = &gcc_pcie0_axi_s_bridge_clk.clkr, + [GCC_CRYPTO_PPE_CLK] = &gcc_crypto_ppe_clk.clkr, ++ [GCC_SNOC_BUS_TIMEOUT2_AHB_CLK] = &gcc_snoc_bus_timeout2_ahb_clk.clkr, ++ [GCC_SNOC_BUS_TIMEOUT3_AHB_CLK] = &gcc_snoc_bus_timeout3_ahb_clk.clkr, ++ [GCC_DCC_CLK] = &gcc_dcc_clk.clkr, ++ [QDSS_TSCTR_CLK_SRC] = &qdss_tsctr_clk_src.clkr, ++ [QDSS_AT_CLK_SRC] = &qdss_at_clk_src.clkr, ++ [GCC_QDSS_AT_CLK] = &gcc_qdss_at_clk.clkr, ++ [GCC_QDSS_DAP_CLK] = &gcc_qdss_dap_clk.clkr, ++ [ADSS_PWM_CLK_SRC] = &adss_pwm_clk_src.clkr, ++ [GCC_ADSS_PWM_CLK] = &gcc_adss_pwm_clk.clkr, + }; + + static const struct qcom_reset_map gcc_ipq8074_resets[] = { +--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +@@ -230,10 +230,19 @@ + #define GCC_GP1_CLK 221 + #define GCC_GP2_CLK 222 + #define GCC_GP3_CLK 223 +-#define GCC_PCIE0_AXI_S_BRIDGE_CLK 224 +-#define GCC_PCIE0_RCHNG_CLK_SRC 225 +-#define GCC_PCIE0_RCHNG_CLK 226 +-#define GCC_CRYPTO_PPE_CLK 227 ++#define GCC_CRYPTO_PPE_CLK 224 ++#define GCC_PCIE0_RCHNG_CLK_SRC 225 ++#define GCC_PCIE0_RCHNG_CLK 226 ++#define GCC_PCIE0_AXI_S_BRIDGE_CLK 227 ++#define GCC_SNOC_BUS_TIMEOUT2_AHB_CLK 228 ++#define GCC_SNOC_BUS_TIMEOUT3_AHB_CLK 229 ++#define GCC_DCC_CLK 230 ++#define ADSS_PWM_CLK_SRC 231 ++#define GCC_ADSS_PWM_CLK 232 ++#define QDSS_TSCTR_CLK_SRC 233 ++#define QDSS_AT_CLK_SRC 234 ++#define GCC_QDSS_AT_CLK 235 ++#define GCC_QDSS_DAP_CLK 236 + + #define GCC_BLSP1_BCR 0 + #define GCC_BLSP1_QUP1_BCR 1 diff --git a/target/linux/ipq807x/patches-5.15/0152-clk-qcom-ipq8074-Fix-gcc_snoc_bus_timeout_ahb_clk-of.patch b/target/linux/ipq807x/patches-5.15/0152-clk-qcom-ipq8074-Fix-gcc_snoc_bus_timeout_ahb_clk-of.patch new file mode 100644 index 000000000..4d1568f35 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0152-clk-qcom-ipq8074-Fix-gcc_snoc_bus_timeout_ahb_clk-of.patch @@ -0,0 +1,44 @@ +From 462aa0c53397ec5bf78e3e7f68aa8a3ca300f4ba Mon Sep 17 00:00:00 2001 +From: Selvam Sathappan Periakaruppan +Date: Tue, 24 Mar 2020 19:09:38 +0530 +Subject: [PATCH 5/8] clk: qcom: ipq8074: Fix gcc_snoc_bus_timeout_ahb_clk + offset + +By default, the ipq8074 V2 clks are provided in the gcc driver. +Updating the gcc_snoc_bus_timeout_ahb_clk offsets also as needed +in ipq8074 V2. + +Change-Id: I5a6e98d002f5c3354a804e55dd9ebb1f83f7f974 +Signed-off-by: Selvam Sathappan Periakaruppan +--- + drivers/clk/qcom/gcc-ipq8074.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4523,10 +4523,10 @@ static const struct alpha_pll_config nss + }; + + static struct clk_branch gcc_snoc_bus_timeout2_ahb_clk = { +- .halt_reg = 0x4700c, ++ .halt_reg = 0x47014, + .halt_bit = 31, + .clkr = { +- .enable_reg = 0x4700c, ++ .enable_reg = 0x47014, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_snoc_bus_timeout2_ahb_clk", +@@ -4541,10 +4541,10 @@ static struct clk_branch gcc_snoc_bus_ti + }; + + static struct clk_branch gcc_snoc_bus_timeout3_ahb_clk = { +- .halt_reg = 0x47014, ++ .halt_reg = 0x4701C, + .halt_bit = 31, + .clkr = { +- .enable_reg = 0x47014, ++ .enable_reg = 0x4701C, + .enable_mask = BIT(0), + .hw.init = &(struct clk_init_data){ + .name = "gcc_snoc_bus_timeout3_ahb_clk", diff --git a/target/linux/ipq807x/patches-5.15/0154-clk-ipq8074-defer-from-disabling-gcc_sleep_clk_src.patch b/target/linux/ipq807x/patches-5.15/0154-clk-ipq8074-defer-from-disabling-gcc_sleep_clk_src.patch new file mode 100644 index 000000000..dfaf31c3f --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0154-clk-ipq8074-defer-from-disabling-gcc_sleep_clk_src.patch @@ -0,0 +1,31 @@ +From db9c60394765843f6a77833bc40c27fac8852e97 Mon Sep 17 00:00:00 2001 +From: Balaji Prakash J +Date: Mon, 20 Apr 2020 20:07:51 +0530 +Subject: [PATCH] clk: ipq8074: defer from disabling gcc_sleep_clk_src + +Added CLK_IS_CRITICAL flag in order to defer from +disabling the sleep clock source. + +Once the usb sleep clocks are disabled, clock framework +is trying to disable the sleep clock source also and +the below warning is observed. + +[ 28.235750] gcc_sleep_clk_src status stuck at 'on' +[ 28.235794] WARNING: CPU: 0 PID: 29 at drivers/clk/qcom/clk-branch.c:92 clk_branch_toggle+0x160/0x178 + +Signed-off-by: Balaji Prakash J +Change-Id: I61fab902375716272ad9c426ce71581058f7bd35 +--- + 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 +@@ -678,6 +678,7 @@ static struct clk_branch gcc_sleep_clk_s + }, + .num_parents = 1, + .ops = &clk_branch2_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }, + }; diff --git a/target/linux/ipq807x/patches-5.15/0155-clk-ipq8074-fix-gcc_blsp1_ahb_clk-properties.patch b/target/linux/ipq807x/patches-5.15/0155-clk-ipq8074-fix-gcc_blsp1_ahb_clk-properties.patch new file mode 100644 index 000000000..2c8887e33 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0155-clk-ipq8074-fix-gcc_blsp1_ahb_clk-properties.patch @@ -0,0 +1,41 @@ +From 52315bec6ed633b6a71f28b746029602f8bd70b9 Mon Sep 17 00:00:00 2001 +From: Balaji Prakash J +Date: Wed, 22 Apr 2020 20:35:30 +0530 +Subject: [PATCH] clk: ipq8074: fix gcc_blsp1_ahb_clk properties + +All the voting enabled clocks does not support the enable +from CBCR register. So, updated gcc_blsp1_ahb_clk enable +register and mask to enable bit in APCS_CLOCK_BRANCH_ENA_VOTE. + +Also, the voting controlled clocks are shared among multiple +components like APSS, RPM, NSS, TZ, etc. So, turning the +voting off from APSS does not make the clock off if it has +been voted from another component. Added the flag +BRANCH_HALT_VOTED in order to skip checking the clock +disable status. + +This change is referred from the below commits, +1. 246b4fb3af9bd65d8af794aac2f0e7b1ed9cc2dd +2. c8374157d5ae91d3b3e0d513d62808a798b32d3a + +Signed-off-by: Balaji Prakash J +Change-Id: I505cb560b31ad27a02c165fbe13bb33a2fc7d230 +--- + drivers/clk/qcom/gcc-ipq8074.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -2118,9 +2118,10 @@ struct clk_rcg2 adss_pwm_clk_src = { + + static struct clk_branch gcc_blsp1_ahb_clk = { + .halt_reg = 0x01008, ++ .halt_check = BRANCH_HALT_VOTED, + .clkr = { +- .enable_reg = 0x01008, +- .enable_mask = BIT(0), ++ .enable_reg = 0x0b004, ++ .enable_mask = BIT(10), + .hw.init = &(struct clk_init_data){ + .name = "gcc_blsp1_ahb_clk", + .parent_names = (const char *[]){ diff --git a/target/linux/ipq807x/patches-5.15/0156-clk-qcom-fix-wrong-RCG-clock-rate-for-high-parent-fr.patch b/target/linux/ipq807x/patches-5.15/0156-clk-qcom-fix-wrong-RCG-clock-rate-for-high-parent-fr.patch new file mode 100644 index 000000000..eb473410c --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0156-clk-qcom-fix-wrong-RCG-clock-rate-for-high-parent-fr.patch @@ -0,0 +1,49 @@ +From 474740fac667ccf7a6b3c748d851e5ed364d59eb Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Mon, 4 Sep 2017 15:00:10 +0530 +Subject: [PATCH 1/3] clk: qcom: fix wrong RCG clock rate for high parent freq + +If the parent clock rate is greater than unsigned long max +divided by 2 then the integer overflow is happening while +calculating the clock rate. Since RCG2 uses half integer +dividers, the clock rate is first being multiplied by 2 +followed by division and this multiplication leads to +overflow. + +Change-Id: I4e4f41b4a539446b962eb684761a3aad6f8a8977 +Signed-off-by: Abhishek Sahu +(cherry picked from commit 9cfedaf465eb18ef31e4d677cba5f3147fe6d430) +Signed-off-by: Praveenkumar I + +Change-Id: I69b78616f468bb7a9647c7994a8579b97c376d4e +--- + drivers/clk/qcom/clk-rcg2.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +--- a/drivers/clk/qcom/clk-rcg2.c ++++ b/drivers/clk/qcom/clk-rcg2.c +@@ -145,18 +145,18 @@ static int clk_rcg2_set_parent(struct cl + * hid_div n + */ + static unsigned long +-calc_rate(unsigned long rate, u32 m, u32 n, u32 mode, u32 hid_div) ++calc_rate(unsigned long parent_rate, u32 m, u32 n, u32 mode, u32 hid_div) + { ++ u64 rate = parent_rate; ++ + if (hid_div) { + rate *= 2; +- rate /= hid_div + 1; ++ do_div(rate, hid_div + 1); + } + + if (mode) { +- u64 tmp = rate; +- tmp *= m; +- do_div(tmp, n); +- rate = tmp; ++ rate *= m; ++ do_div(rate, n); + } + + return rate; diff --git a/target/linux/ipq807x/patches-5.15/0157-clk-qcom-add-support-for-hw-controlled-RCG.patch b/target/linux/ipq807x/patches-5.15/0157-clk-qcom-add-support-for-hw-controlled-RCG.patch new file mode 100644 index 000000000..b1b8bb4d4 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0157-clk-qcom-add-support-for-hw-controlled-RCG.patch @@ -0,0 +1,136 @@ +From 0245360f8e118b67f4015533cfc79314f2d848d5 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Tue, 13 Jun 2017 15:30:39 +0530 +Subject: [PATCH 2/3] clk: qcom: add support for hw controlled RCG +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The current driver generates stack trace during RCG update if +the RCG is off and new parent source is also disabled. For +hardware controlled RCG’s, clock is forced on during update +process and goes back to off status once switch is completed. +Since the new parent is in disabled state so update bit won’t +be cleared in this case. The check for update bit can be +skipped in this case. + +Signed-off-by: Abhishek Sahu +(cherry picked from commit 84dd0e12f10eebff44a464eb8455205abc4b4178) +Signed-off-by: Praveenkumar I + +Change-Id: Ifb4175b02d89542baa1b758107c2ce86f7bf8599 +--- + drivers/clk/qcom/clk-rcg.h | 4 ++++ + drivers/clk/qcom/clk-rcg2.c | 27 +++++++++++++++++++++------ + 2 files changed, 25 insertions(+), 6 deletions(-) + +--- a/drivers/clk/qcom/clk-rcg.h ++++ b/drivers/clk/qcom/clk-rcg.h +@@ -135,6 +135,7 @@ extern const struct clk_ops clk_dyn_rcg_ + * @mnd_width: number of bits in m/n/d values + * @hid_width: number of bits in half integer divider + * @safe_src_index: safe src index value ++ * @flags: RCG2 specific clock flags + * @parent_map: map from software's parent index to hardware's src_sel field + * @freq_tbl: frequency table + * @clkr: regmap clock handle +@@ -145,6 +146,9 @@ struct clk_rcg2 { + u8 mnd_width; + u8 hid_width; + u8 safe_src_index; ++ ++#define CLK_RCG2_HW_CONTROLLED BIT(0) ++ u8 flags; + const struct parent_map *parent_map; + const struct freq_tbl *freq_tbl; + struct clk_regmap clkr; +--- a/drivers/clk/qcom/clk-rcg2.c ++++ b/drivers/clk/qcom/clk-rcg2.c +@@ -97,7 +97,7 @@ err: + return 0; + } + +-static int update_config(struct clk_rcg2 *rcg) ++static int update_config(struct clk_rcg2 *rcg, bool check_update_clear) + { + int count, ret; + u32 cmd; +@@ -109,6 +109,9 @@ static int update_config(struct clk_rcg2 + if (ret) + return ret; + ++ if (!check_update_clear) ++ return 0; ++ + /* Wait for update to take effect */ + for (count = 500; count > 0; count--) { + ret = regmap_read(rcg->clkr.regmap, rcg->cmd_rcgr + CMD_REG, &cmd); +@@ -127,14 +130,19 @@ static int clk_rcg2_set_parent(struct cl + { + struct clk_rcg2 *rcg = to_clk_rcg2(hw); + int ret; ++ bool check_update_clear = true; + u32 cfg = rcg->parent_map[index].cfg << CFG_SRC_SEL_SHIFT; + ++ if ((rcg->flags & CLK_RCG2_HW_CONTROLLED) && ++ !clk_hw_is_enabled(clk_hw_get_parent_by_index(hw, index))) ++ check_update_clear = false; ++ + ret = regmap_update_bits(rcg->clkr.regmap, RCG_CFG_OFFSET(rcg), + CFG_SRC_SEL_MASK, cfg); + if (ret) + return ret; + +- return update_config(rcg); ++ return update_config(rcg, check_update_clear); + } + + /* +@@ -311,12 +319,19 @@ static int __clk_rcg2_configure(struct c + static int clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) + { + int ret; ++ bool check_update_clear = true; ++ struct clk_hw *hw = &rcg->clkr.hw; ++ int index = qcom_find_src_index(hw, rcg->parent_map, f->src); + + ret = __clk_rcg2_configure(rcg, f); + if (ret) + return ret; + +- return update_config(rcg); ++ if ((rcg->flags & CLK_RCG2_HW_CONTROLLED) && ++ !clk_hw_is_enabled(clk_hw_get_parent_by_index(hw, index))) ++ check_update_clear = false; ++ ++ return update_config(rcg, check_update_clear); + } + + static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate, +@@ -895,7 +910,7 @@ static int clk_gfx3d_set_rate_and_parent + if (ret) + return ret; + +- return update_config(rcg); ++ return update_config(rcg, true); + } + + static int clk_gfx3d_set_rate(struct clk_hw *hw, unsigned long rate, +@@ -1007,7 +1022,7 @@ static int clk_rcg2_shared_enable(struct + if (ret) + return ret; + +- ret = update_config(rcg); ++ ret = update_config(rcg, true); + if (ret) + return ret; + +@@ -1038,7 +1053,7 @@ static void clk_rcg2_shared_disable(stru + regmap_write(rcg->clkr.regmap, rcg->cmd_rcgr + CFG_REG, + rcg->safe_src_index << CFG_SRC_SEL_SHIFT); + +- update_config(rcg); ++ update_config(rcg, true); + + clk_rcg2_clear_force_enable(hw); + diff --git a/target/linux/ipq807x/patches-5.15/0158-clk-qcom-ipq8074-add-hw-controlled-flag.patch b/target/linux/ipq807x/patches-5.15/0158-clk-qcom-ipq8074-add-hw-controlled-flag.patch new file mode 100644 index 000000000..3ea835c8c --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0158-clk-qcom-ipq8074-add-hw-controlled-flag.patch @@ -0,0 +1,38 @@ +From 18d04f5cae30725ffa0c1c025f6beb1821c46857 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Tue, 13 Jun 2017 15:31:34 +0530 +Subject: [PATCH 3/3] clk: qcom: ipq8074: add hw controlled flag +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +These RCG’s are hw controlled so add the +CLK_RCG2_HW_CONTROLLED flag. + +Signed-off-by: Abhishek Sahu +(cherry picked from commit 9a025b8271a95a80e9e769b89154b98b263be860) +Signed-off-by: Praveenkumar I + +Change-Id: Ic5da1551bf46921890955312026b9175a42fe14e +--- + drivers/clk/qcom/gcc-ipq8074.c | 2 ++ + 1 file changed, 2 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -643,6 +643,7 @@ static struct clk_rcg2 pcnoc_bfdcd_clk_s + .freq_tbl = ftbl_pcnoc_bfdcd_clk_src, + .hid_width = 5, + .parent_map = gcc_xo_gpll0_gpll0_out_main_div2_map, ++ .flags = CLK_RCG2_HW_CONTROLLED, + .clkr.hw.init = &(struct clk_init_data){ + .name = "pcnoc_bfdcd_clk_src", + .parent_names = gcc_xo_gpll0_gpll0_out_main_div2, +@@ -1317,6 +1318,7 @@ static struct clk_rcg2 system_noc_bfdcd_ + .freq_tbl = ftbl_system_noc_bfdcd_clk_src, + .hid_width = 5, + .parent_map = gcc_xo_gpll0_gpll6_gpll0_out_main_div2_map, ++ .flags = CLK_RCG2_HW_CONTROLLED, + .clkr.hw.init = &(struct clk_init_data){ + .name = "system_noc_bfdcd_clk_src", + .parent_names = gcc_xo_gpll0_gpll6_gpll0_out_main_div2, diff --git a/target/linux/ipq807x/patches-5.15/0159-clk-qcom-clk-rcg2-fix-set-duty-cycle-call.patch b/target/linux/ipq807x/patches-5.15/0159-clk-qcom-clk-rcg2-fix-set-duty-cycle-call.patch new file mode 100644 index 000000000..b27859c7c --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/0159-clk-qcom-clk-rcg2-fix-set-duty-cycle-call.patch @@ -0,0 +1,11 @@ +--- a/drivers/clk/qcom/clk-rcg2.c ++++ b/drivers/clk/qcom/clk-rcg2.c +@@ -455,7 +455,7 @@ static int clk_rcg2_set_duty_cycle(struc + if (ret) + return ret; + +- return update_config(rcg); ++ return update_config(rcg, true); + } + + const struct clk_ops clk_rcg2_ops = { diff --git a/target/linux/ipq807x/patches-5.15/300-regulator-add-Qualcomm-CPR-regulators.patch b/target/linux/ipq807x/patches-5.15/300-regulator-add-Qualcomm-CPR-regulators.patch new file mode 100644 index 000000000..a3df7474b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/300-regulator-add-Qualcomm-CPR-regulators.patch @@ -0,0 +1,66 @@ +From c013e84fa1cc1b8cd1ded28639a0f9745360aae6 Mon Sep 17 00:00:00 2001 +From: Robert Marko +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 +--- + drivers/regulator/Kconfig | 33 +++++++++++++++++++++++++++++++++ + drivers/regulator/Makefile | 3 +++ + 2 files changed, 36 insertions(+) + +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -1423,5 +1423,38 @@ 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 +@@ -105,6 +105,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 diff --git a/target/linux/ipq807x/patches-5.15/301-power-Add-Qualcomm-APM.patch b/target/linux/ipq807x/patches-5.15/301-power-Add-Qualcomm-APM.patch new file mode 100644 index 000000000..a7ff87826 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/301-power-Add-Qualcomm-APM.patch @@ -0,0 +1,27 @@ +From d90ef31dbb0212b20099a07d34f27dbeb06c5c74 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 11 Apr 2022 14:38:08 +0200 +Subject: [PATCH] power: Add Qualcomm APM + +Allow building Qualcomm APM. + +Signed-off-by: Robert Marko +--- + drivers/power/Kconfig | 1 + + drivers/power/Makefile | 1 + + 2 files changed, 2 insertions(+) + +--- 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/ diff --git a/target/linux/ipq807x/patches-5.15/600-5.15-qca-nss-ecm-support-CORE.patch b/target/linux/ipq807x/patches-5.15/600-5.15-qca-nss-ecm-support-CORE.patch new file mode 100644 index 000000000..59b78cf33 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/600-5.15-qca-nss-ecm-support-CORE.patch @@ -0,0 +1,615 @@ +--- a/include/linux/if_bridge.h ++++ b/include/linux/if_bridge.h +@@ -69,6 +69,9 @@ void brioctl_set(int (*hook)(struct net + void __user *uarg)); + int br_ioctl_call(struct net *net, struct net_bridge *br, unsigned int cmd, + struct ifreq *ifr, void __user *uarg); ++extern void br_dev_update_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *nlstats); ++extern bool br_is_hairpin_enabled(struct net_device *dev); + + extern void br_dev_update_stats(struct net_device *dev, + struct rtnl_link_stats64 *nlstats); +@@ -195,4 +198,42 @@ static inline clock_t br_get_ageing_time + } + #endif + ++/* QCA NSS ECM support - Start */ ++extern struct net_device *br_port_dev_get(struct net_device *dev, ++ unsigned char *addr, ++ struct sk_buff *skb, ++ unsigned int cookie); ++extern void br_refresh_fdb_entry(struct net_device *dev, const char *addr); ++extern void br_fdb_entry_refresh(struct net_device *dev, const char *addr, __u16 vid); ++extern struct net_bridge_fdb_entry *br_fdb_has_entry(struct net_device *dev, ++ const char *addr, ++ __u16 vid); ++extern void br_fdb_update_register_notify(struct notifier_block *nb); ++extern void br_fdb_update_unregister_notify(struct notifier_block *nb); ++ ++typedef struct net_bridge_port *br_port_dev_get_hook_t(struct net_device *dev, ++ struct sk_buff *skb, ++ unsigned char *addr, ++ unsigned int cookie); ++extern br_port_dev_get_hook_t __rcu *br_port_dev_get_hook; ++ ++#define BR_FDB_EVENT_ADD 0x01 ++#define BR_FDB_EVENT_DEL 0x02 ++ ++struct br_fdb_event { ++ struct net_device *dev; ++ unsigned char addr[6]; ++ unsigned char is_local; ++ struct net_bridge *br; ++ struct net_device *orig_dev; ++}; ++extern void br_fdb_register_notify(struct notifier_block *nb); ++extern void br_fdb_unregister_notify(struct notifier_block *nb); ++ ++typedef struct net_bridge_port *br_get_dst_hook_t( ++ const struct net_bridge_port *src, ++ struct sk_buff **skb); ++extern br_get_dst_hook_t __rcu *br_get_dst_hook; ++/* QCA NSS ECM support - End */ ++ + #endif +--- a/include/linux/if_vlan.h ++++ b/include/linux/if_vlan.h +@@ -222,7 +222,28 @@ extern void vlan_vids_del_by_dev(struct + + extern bool vlan_uses_dev(const struct net_device *dev); + ++/* QCA NSS ECM support - Start */ ++extern void __vlan_dev_update_accel_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *stats); ++extern u16 vlan_dev_get_egress_prio(struct net_device *dev, u32 skb_prio); ++extern struct net_device *vlan_dev_next_dev(const struct net_device *dev); ++/* QCA NSS ECM support - End */ ++ + #else ++/* QCA NSS ECM support - Start */ ++static inline void __vlan_dev_update_accel_stats(struct net_device *dev, ++ struct rtnl_link_stats64 *stats) ++{ ++ ++} ++ ++static inline u16 vlan_dev_get_egress_prio(struct net_device *dev, ++ u32 skb_prio) ++{ ++ return 0; ++} ++/* QCA NSS ECM support - End */ ++ + static inline struct net_device * + __vlan_find_dev_deep_rcu(struct net_device *real_dev, + __be16 vlan_proto, u16 vlan_id) +--- a/include/linux/netdevice.h ++++ b/include/linux/netdevice.h +@@ -2834,6 +2834,10 @@ enum netdev_cmd { + NETDEV_CVLAN_FILTER_DROP_INFO, + NETDEV_SVLAN_FILTER_PUSH_INFO, + NETDEV_SVLAN_FILTER_DROP_INFO, ++ /* QCA NSS ECM Support - Start */ ++ NETDEV_BR_JOIN, ++ NETDEV_BR_LEAVE, ++ /* QCA NSS ECM Support - End */ + }; + const char *netdev_cmd_to_name(enum netdev_cmd cmd); + +--- a/include/net/ip6_route.h ++++ b/include/net/ip6_route.h +@@ -211,6 +211,11 @@ void rt6_multipath_rebalance(struct fib6 + void rt6_uncached_list_add(struct rt6_info *rt); + void rt6_uncached_list_del(struct rt6_info *rt); + ++/* QCA NSS ECM support - Start */ ++int rt6_register_notifier(struct notifier_block *nb); ++int rt6_unregister_notifier(struct notifier_block *nb); ++/* QCA NSS ECM support - End */ ++ + static inline const struct rt6_info *skb_rt6_info(const struct sk_buff *skb) + { + const struct dst_entry *dst = skb_dst(skb); +--- a/include/net/neighbour.h ++++ b/include/net/neighbour.h +@@ -574,4 +574,15 @@ static inline void neigh_update_is_route + *notify = 1; + } + } ++ ++/* QCA NSS ECM support - Start */ ++struct neigh_mac_update { ++ unsigned char old_mac[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))]; ++ unsigned char update_mac[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))]; ++}; ++ ++extern void neigh_mac_update_register_notify(struct notifier_block *nb); ++extern void neigh_mac_update_unregister_notify(struct notifier_block *nb); ++/* QCA NSS ECM support - End */ ++ + #endif +--- a/include/net/route.h ++++ b/include/net/route.h +@@ -234,6 +234,11 @@ struct rtable *rt_dst_alloc(struct net_d + bool nopolicy, bool noxfrm); + struct rtable *rt_dst_clone(struct net_device *dev, struct rtable *rt); + ++/* QCA NSS ECM support - Start */ ++int ip_rt_register_notifier(struct notifier_block *nb); ++int ip_rt_unregister_notifier(struct notifier_block *nb); ++/* QCA NSS ECM support - End */ ++ + struct in_ifaddr; + void fib_add_ifaddr(struct in_ifaddr *); + void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *); +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -38,6 +38,35 @@ static int fdb_insert(struct net_bridge + static void fdb_notify(struct net_bridge *br, + const struct net_bridge_fdb_entry *, int, bool); + ++/* QCA NSS ECM support - Start */ ++ATOMIC_NOTIFIER_HEAD(br_fdb_notifier_list); ++ATOMIC_NOTIFIER_HEAD(br_fdb_update_notifier_list); ++ ++void br_fdb_register_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_register(&br_fdb_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(br_fdb_register_notify); ++ ++void br_fdb_unregister_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_unregister(&br_fdb_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(br_fdb_unregister_notify); ++ ++void br_fdb_update_register_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_register(&br_fdb_update_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(br_fdb_update_register_notify); ++ ++void br_fdb_update_unregister_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_unregister(&br_fdb_update_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(br_fdb_update_unregister_notify); ++/* QCA NSS ECM support - End */ ++ + int __init br_fdb_init(void) + { + br_fdb_cache = kmem_cache_create("bridge_fdb_cache", +@@ -343,6 +372,7 @@ void br_fdb_cleanup(struct work_struct * + unsigned long delay = hold_time(br); + unsigned long work_delay = delay; + unsigned long now = jiffies; ++ u8 mac_addr[6]; /* QCA NSS ECM support */ + + /* this part is tricky, in order to avoid blocking learning and + * consequently forwarding, we rely on rcu to delete objects with +@@ -369,8 +399,15 @@ void br_fdb_cleanup(struct work_struct * + work_delay = min(work_delay, this_timer - now); + } else { + spin_lock_bh(&br->hash_lock); +- if (!hlist_unhashed(&f->fdb_node)) ++ if (!hlist_unhashed(&f->fdb_node)) { ++ ether_addr_copy(mac_addr, f->key.addr.addr); + fdb_delete(br, f, true); ++ /* QCA NSS ECM support - Start */ ++ atomic_notifier_call_chain( ++ &br_fdb_update_notifier_list, 0, ++ (void *)mac_addr); ++ /* QCA NSS ECM support - End */ ++ } + spin_unlock_bh(&br->hash_lock); + } + } +@@ -618,6 +655,12 @@ void br_fdb_update(struct net_bridge *br + &fdb->flags))) + clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, + &fdb->flags); ++ ++ /* QCA NSS ECM support - Start */ ++ atomic_notifier_call_chain( ++ &br_fdb_update_notifier_list, ++ 0, (void *)addr); ++ /* QCA NSS ECM support - End */ + } + + if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) +@@ -799,6 +842,25 @@ static void fdb_notify(struct net_bridge + + br_offload_fdb_update(fdb); + ++ /* QCA NSS ECM support - Start */ ++ if (fdb->dst) { ++ int event; ++ struct br_fdb_event fdb_event; ++ ++ if (type == RTM_NEWNEIGH) ++ event = BR_FDB_EVENT_ADD; ++ else ++ event = BR_FDB_EVENT_DEL; ++ ++ fdb_event.dev = fdb->dst->dev; ++ ether_addr_copy(fdb_event.addr, fdb->key.addr.addr); ++ fdb_event.is_local = test_bit(BR_FDB_LOCAL, &fdb->flags); ++ atomic_notifier_call_chain(&br_fdb_notifier_list, ++ event, ++ (void *)&fdb_event); ++ } ++ /* QCA NSS ECM support - End */ ++ + if (swdev_notify) + br_switchdev_fdb_notify(br, fdb, type); + +@@ -1382,3 +1444,62 @@ void br_fdb_clear_offload(const struct n + spin_unlock_bh(&p->br->hash_lock); + } + EXPORT_SYMBOL_GPL(br_fdb_clear_offload); ++ ++/* QCA NSS ECM support - Start */ ++/* Refresh FDB entries for bridge packets being forwarded by offload engines */ ++void br_refresh_fdb_entry(struct net_device *dev, const char *addr) ++{ ++ struct net_bridge_port *p = br_port_get_rcu(dev); ++ ++ if (!p || p->state == BR_STATE_DISABLED) ++ return; ++ ++ if (!is_valid_ether_addr(addr)) { ++ pr_info("bridge: Attempt to refresh with invalid ether address %pM\n", ++ addr); ++ return; ++ } ++ ++ rcu_read_lock(); ++ br_fdb_update(p->br, p, addr, 0, true); ++ rcu_read_unlock(); ++} ++EXPORT_SYMBOL_GPL(br_refresh_fdb_entry); ++ ++/* Update timestamp of FDB entries for bridge packets being forwarded by offload engines */ ++void br_fdb_entry_refresh(struct net_device *dev, const char *addr, __u16 vid) ++{ ++ struct net_bridge_fdb_entry *fdb; ++ struct net_bridge_port *p = br_port_get_rcu(dev); ++ ++ if (!p || p->state == BR_STATE_DISABLED) ++ return; ++ ++ rcu_read_lock(); ++ fdb = fdb_find_rcu(&p->br->fdb_hash_tbl, addr, vid); ++ if (likely(fdb)) { ++ fdb->updated = jiffies; ++ } ++ rcu_read_unlock(); ++} ++EXPORT_SYMBOL_GPL(br_fdb_entry_refresh); ++ ++/* Look up the MAC address in the device's bridge fdb table */ ++struct net_bridge_fdb_entry *br_fdb_has_entry(struct net_device *dev, ++ const char *addr, __u16 vid) ++{ ++ struct net_bridge_port *p = br_port_get_rcu(dev); ++ struct net_bridge_fdb_entry *fdb; ++ ++ if (!p || p->state == BR_STATE_DISABLED) ++ return NULL; ++ ++ rcu_read_lock(); ++ fdb = fdb_find_rcu(&p->br->fdb_hash_tbl, addr, vid); ++ rcu_read_unlock(); ++ ++ return fdb; ++} ++EXPORT_SYMBOL_GPL(br_fdb_has_entry); ++/* QCA NSS ECM support - End */ ++ +--- a/net/bridge/br_if.c ++++ b/net/bridge/br_if.c +@@ -27,6 +27,12 @@ + #include "br_private.h" + #include "br_private_offload.h" + ++/* QCA NSS ECM support - Start */ ++/* Hook for external forwarding logic */ ++br_port_dev_get_hook_t __rcu *br_port_dev_get_hook __read_mostly; ++EXPORT_SYMBOL_GPL(br_port_dev_get_hook); ++/* QCA NSS ECM support - End */ ++ + /* + * Determine initial path cost based on speed. + * using recommendations from 802.1d standard +@@ -707,6 +713,8 @@ int br_add_if(struct net_bridge *br, str + + kobject_uevent(&p->kobj, KOBJ_ADD); + ++ call_netdevice_notifiers(NETDEV_BR_JOIN, dev); /* QCA NSS ECM support */ ++ + return 0; + + err6: +@@ -742,6 +750,8 @@ int br_del_if(struct net_bridge *br, str + if (!p || p->br != br) + return -EINVAL; + ++ call_netdevice_notifiers(NETDEV_BR_LEAVE, dev); /* QCA NSS ECM support */ ++ + /* Since more than one interface can be attached to a bridge, + * there still maybe an alternate path for netconsole to use; + * therefore there is no reason for a NETDEV_RELEASE event. +@@ -810,3 +820,74 @@ bool br_port_flag_is_set(const struct ne + return p->flags & flag; + } + EXPORT_SYMBOL_GPL(br_port_flag_is_set); ++ ++/* API to know if hairpin feature is enabled/disabled on this bridge port */ ++bool br_is_hairpin_enabled(struct net_device *dev) ++{ ++ struct net_bridge_port *port = br_port_get_check_rcu(dev); ++ ++ if (likely(port)) ++ return port->flags & BR_HAIRPIN_MODE; ++ return false; ++} ++EXPORT_SYMBOL_GPL(br_is_hairpin_enabled); ++ ++/* br_port_dev_get() ++ * If a skb is provided, and the br_port_dev_get_hook_t hook exists, ++ * use that to try and determine the egress port for that skb. ++ * If not, or no egress port could be determined, use the given addr ++ * to identify the port to which it is reachable, ++ * returing a reference to the net device associated with that port. ++ * ++ * NOTE: Return NULL if given dev is not a bridge or the mac has no ++ * associated port. ++ */ ++struct net_device *br_port_dev_get(struct net_device *dev, unsigned char *addr, ++ struct sk_buff *skb, ++ unsigned int cookie) ++{ ++ struct net_bridge_fdb_entry *fdbe; ++ struct net_bridge *br; ++ struct net_device *netdev = NULL; ++ ++ /* Is this a bridge? */ ++ if (!(dev->priv_flags & IFF_EBRIDGE)) ++ return NULL; ++ ++ rcu_read_lock(); ++ ++ /* If the hook exists and the skb isn't NULL, try and get the port */ ++ if (skb) { ++ br_port_dev_get_hook_t *port_dev_get_hook; ++ ++ port_dev_get_hook = rcu_dereference(br_port_dev_get_hook); ++ if (port_dev_get_hook) { ++ struct net_bridge_port *pdst = ++ __br_get(port_dev_get_hook, NULL, dev, skb, ++ addr, cookie); ++ if (pdst) { ++ dev_hold(pdst->dev); ++ netdev = pdst->dev; ++ goto out; ++ } ++ } ++ } ++ ++ /* Either there is no hook, or can't ++ * determine the port to use - fall back to using FDB ++ */ ++ ++ br = netdev_priv(dev); ++ ++ /* Lookup the fdb entry and get reference to the port dev */ ++ fdbe = br_fdb_find_rcu(br, addr, 0); ++ if (fdbe && fdbe->dst) { ++ netdev = fdbe->dst->dev; /* port device */ ++ dev_hold(netdev); ++ } ++out: ++ rcu_read_unlock(); ++ return netdev; ++} ++EXPORT_SYMBOL_GPL(br_port_dev_get); ++/* QCA NSS ECM support - End */ +--- a/net/bridge/br_private.h ++++ b/net/bridge/br_private.h +@@ -2122,4 +2122,9 @@ void br_do_proxy_suppress_arp(struct sk_ + void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, + u16 vid, struct net_bridge_port *p, struct nd_msg *msg); + struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb, struct nd_msg *m); ++ ++/* QCA NSS ECM support - Start */ ++#define __br_get(__hook, __default, __args ...) \ ++ (__hook ? (__hook(__args)) : (__default)) ++/* QCA NSS ECM support - End */ + #endif +--- a/net/core/neighbour.c ++++ b/net/core/neighbour.c +@@ -1204,7 +1204,21 @@ static void neigh_update_hhs(struct neig + } + } + ++/* QCA NSS ECM support - start */ ++ATOMIC_NOTIFIER_HEAD(neigh_mac_update_notifier_list); ++ ++void neigh_mac_update_register_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_register(&neigh_mac_update_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(neigh_mac_update_register_notify); + ++void neigh_mac_update_unregister_notify(struct notifier_block *nb) ++{ ++ atomic_notifier_chain_unregister(&neigh_mac_update_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(neigh_mac_update_unregister_notify); ++/* QCA NSS ECM support - End */ + + /* Generic update routine. + -- lladdr is new lladdr or NULL, if it is not supplied. +@@ -1235,6 +1249,7 @@ static int __neigh_update(struct neighbo + int notify = 0; + struct net_device *dev; + int update_isrouter = 0; ++ struct neigh_mac_update nmu; /* QCA NSS ECM support */ + + trace_neigh_update(neigh, lladdr, new, flags, nlmsg_pid); + +@@ -1249,6 +1264,8 @@ static int __neigh_update(struct neighbo + new = old; + goto out; + } ++ memset(&nmu, 0, sizeof(struct neigh_mac_update)); /* QCA NSS ECM support */ ++ + if (!(flags & NEIGH_UPDATE_F_ADMIN) && + (old & (NUD_NOARP | NUD_PERMANENT))) + goto out; +@@ -1286,6 +1303,11 @@ static int __neigh_update(struct neighbo + - compare new & old + - if they are different, check override flag + */ ++ /* QCA NSS ECM update - Start */ ++ memcpy(nmu.old_mac, neigh->ha, dev->addr_len); ++ memcpy(nmu.update_mac, lladdr, dev->addr_len); ++ /* QCA NSS ECM update - End */ ++ + if ((old & NUD_VALID) && + !memcmp(lladdr, neigh->ha, dev->addr_len)) + lladdr = neigh->ha; +@@ -1408,8 +1430,11 @@ out: + if (((new ^ old) & NUD_PERMANENT) || ext_learn_change) + neigh_update_gc_list(neigh); + +- if (notify) ++ if (notify) { + neigh_update_notify(neigh, nlmsg_pid); ++ atomic_notifier_call_chain(&neigh_mac_update_notifier_list, 0, ++ (struct neigh_mac_update *)&nmu); /* QCA NSS ECM support */ ++ } + + trace_neigh_update_done(neigh, err); + +--- a/net/ipv4/fib_trie.c ++++ b/net/ipv4/fib_trie.c +@@ -1203,6 +1203,9 @@ static bool fib_valid_key_len(u32 key, u + static void fib_remove_alias(struct trie *t, struct key_vector *tp, + struct key_vector *l, struct fib_alias *old); + ++/* Define route change notification chain. */ ++static BLOCKING_NOTIFIER_HEAD(iproute_chain); /* QCA NSS ECM support */ ++ + /* Caller must hold RTNL. */ + int fib_table_insert(struct net *net, struct fib_table *tb, + struct fib_config *cfg, struct netlink_ext_ack *extack) +@@ -1393,6 +1396,9 @@ int fib_table_insert(struct net *net, st + rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, new_fa->tb_id, + &cfg->fc_nlinfo, nlflags); + succeeded: ++ blocking_notifier_call_chain(&iproute_chain, ++ RTM_NEWROUTE, fi); ++ + return 0; + + out_remove_new_fa: +@@ -1763,6 +1769,9 @@ int fib_table_delete(struct net *net, st + if (fa_to_delete->fa_state & FA_S_ACCESSED) + rt_cache_flush(cfg->fc_nlinfo.nl_net); + ++ blocking_notifier_call_chain(&iproute_chain, ++ RTM_DELROUTE, fa_to_delete->fa_info); ++ + fib_release_info(fa_to_delete->fa_info); + alias_free_mem_rcu(fa_to_delete); + return 0; +@@ -2391,6 +2400,20 @@ void __init fib_trie_init(void) + 0, SLAB_PANIC | SLAB_ACCOUNT, NULL); + } + ++/* QCA NSS ECM support - Start */ ++int ip_rt_register_notifier(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_register(&iproute_chain, nb); ++} ++EXPORT_SYMBOL(ip_rt_register_notifier); ++ ++int ip_rt_unregister_notifier(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_unregister(&iproute_chain, nb); ++} ++EXPORT_SYMBOL(ip_rt_unregister_notifier); ++/* QCA NSS ECM support - End */ ++ + struct fib_table *fib_trie_table(u32 id, struct fib_table *alias) + { + struct fib_table *tb; +--- a/include/net/addrconf.h ++++ b/include/net/addrconf.h +@@ -501,4 +501,9 @@ int if6_proc_init(void); + void if6_proc_exit(void); + #endif + ++/* QCA NSS ECM support - Start */ ++struct net_device *ipv6_dev_find_and_hold(struct net *net, struct in6_addr *addr, ++ int strict); ++/* QCA NSS ECM support - End */ ++ + #endif +--- a/net/ipv6/route.c ++++ b/net/ipv6/route.c +@@ -3878,6 +3878,9 @@ out_free: + return ERR_PTR(err); + } + ++/* Define route change notification chain. */ ++ATOMIC_NOTIFIER_HEAD(ip6route_chain); /* QCA NSS ECM support */ ++ + int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, + struct netlink_ext_ack *extack) + { +@@ -3889,6 +3892,10 @@ int ip6_route_add(struct fib6_config *cf + return PTR_ERR(rt); + + err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack); ++ if (!err) ++ atomic_notifier_call_chain(&ip6route_chain, ++ RTM_NEWROUTE, rt); ++ + fib6_info_release(rt); + + return err; +@@ -3910,6 +3917,9 @@ static int __ip6_del_rt(struct fib6_info + err = fib6_del(rt, info); + spin_unlock_bh(&table->tb6_lock); + ++ if (!err) ++ atomic_notifier_call_chain(&ip6route_chain, ++ RTM_DELROUTE, rt); + out: + fib6_info_release(rt); + return err; +@@ -6352,6 +6362,20 @@ static int ip6_route_dev_notify(struct n + return NOTIFY_OK; + } + ++/* QCA NSS ECM support - Start */ ++int rt6_register_notifier(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_register(&ip6route_chain, nb); ++} ++EXPORT_SYMBOL(rt6_register_notifier); ++ ++int rt6_unregister_notifier(struct notifier_block *nb) ++{ ++ return atomic_notifier_chain_unregister(&ip6route_chain, nb); ++} ++EXPORT_SYMBOL(rt6_unregister_notifier); ++/* QCA NSS ECM support - End */ ++ + /* + * /proc + */ diff --git a/target/linux/ipq807x/patches-5.15/601-5.15-netfilter-export-udp_get_timeouts-function.patch b/target/linux/ipq807x/patches-5.15/601-5.15-netfilter-export-udp_get_timeouts-function.patch new file mode 100644 index 000000000..c0504805b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/601-5.15-netfilter-export-udp_get_timeouts-function.patch @@ -0,0 +1,35 @@ +From 9d2a00412fde5f5d7dbd341f42acb6799c2db65c Mon Sep 17 00:00:00 2001 +From: Simon Casey +Date: Wed, 2 Feb 2022 19:30:09 +0100 +Subject: [PATCH] Update 601-netfilter-export-udp_get_timeouts-function.patch + for kernel 5.15 + +--- + include/net/netfilter/nf_conntrack_timeout.h | 1 + + net/netfilter/nf_conntrack_proto_udp.c | 3 ++- + 2 files changed, 3 insertions(+), 1 deletion(-) + +--- a/include/net/netfilter/nf_conntrack_timeout.h ++++ b/include/net/netfilter/nf_conntrack_timeout.h +@@ -123,5 +123,6 @@ static inline void nf_ct_destroy_timeout + extern struct nf_ct_timeout *(*nf_ct_timeout_find_get_hook)(struct net *net, const char *name); + extern void (*nf_ct_timeout_put_hook)(struct nf_ct_timeout *timeout); + #endif ++extern unsigned int *udp_get_timeouts(struct net *net); + + #endif /* _NF_CONNTRACK_TIMEOUT_H */ +--- a/net/netfilter/nf_conntrack_proto_udp.c ++++ b/net/netfilter/nf_conntrack_proto_udp.c +@@ -29,10 +29,11 @@ static const unsigned int udp_timeouts[U + [UDP_CT_REPLIED] = 120*HZ, + }; + +-static unsigned int *udp_get_timeouts(struct net *net) ++unsigned int *udp_get_timeouts(struct net *net) + { + return nf_udp_pernet(net)->timeouts; + } ++EXPORT_SYMBOL(udp_get_timeouts); + + static void udp_error_log(const struct sk_buff *skb, + const struct nf_hook_state *state, diff --git a/target/linux/ipq807x/patches-5.15/602-5.15-qca-add-pppoe-offload-support.patch b/target/linux/ipq807x/patches-5.15/602-5.15-qca-add-pppoe-offload-support.patch new file mode 100644 index 000000000..88693425d --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/602-5.15-qca-add-pppoe-offload-support.patch @@ -0,0 +1,602 @@ +From ac4b71aecf237fd07a29788706d198b4e36fa660 Mon Sep 17 00:00:00 2001 +From: Simon Casey +Date: Wed, 2 Feb 2022 19:32:54 +0100 +Subject: [PATCH] Update 602-qca-add-pppoe-offload-support.patch for kernel + 5.15 + +--- + drivers/net/ppp/ppp_generic.c | 276 +++++++++++++++++++++++++++++++++- + drivers/net/ppp/pppoe.c | 82 +++++++++- + include/linux/if_pppox.h | 13 ++ + include/linux/netdevice.h | 19 +++ + include/linux/ppp_channel.h | 63 +++++++- + 5 files changed, 445 insertions(+), 8 deletions(-) + +--- a/drivers/net/ppp/ppp_generic.c ++++ b/drivers/net/ppp/ppp_generic.c +@@ -48,6 +48,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -253,6 +254,25 @@ struct ppp_net { + #define seq_before(a, b) ((s32)((a) - (b)) < 0) + #define seq_after(a, b) ((s32)((a) - (b)) > 0) + ++ ++/* ++ * Registration/Unregistration methods ++ * for PPP channel connect and disconnect event notifications. ++ */ ++RAW_NOTIFIER_HEAD(ppp_channel_connection_notifier_list); ++ ++void ppp_channel_connection_register_notify(struct notifier_block *nb) ++{ ++ raw_notifier_chain_register(&ppp_channel_connection_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(ppp_channel_connection_register_notify); ++ ++void ppp_channel_connection_unregister_notify(struct notifier_block *nb) ++{ ++ raw_notifier_chain_unregister(&ppp_channel_connection_notifier_list, nb); ++} ++EXPORT_SYMBOL_GPL(ppp_channel_connection_unregister_notify); ++ + /* Prototypes. */ + static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf, + struct file *file, unsigned int cmd, unsigned long arg); +@@ -3450,7 +3470,10 @@ ppp_connect_channel(struct channel *pch, + struct ppp_net *pn; + int ret = -ENXIO; + int hdrlen; ++ int ppp_proto; ++ int version; + ++ int notify = 0; + pn = ppp_pernet(pch->chan_net); + + mutex_lock(&pn->all_ppp_mutex); +@@ -3482,13 +3505,40 @@ ppp_connect_channel(struct channel *pch, + ++ppp->n_channels; + pch->ppp = ppp; + refcount_inc(&ppp->file.refcnt); ++ ++ /* Set the netdev priv flag if the prototype ++ * is L2TP or PPTP. Return success in all cases ++ */ ++ if (!pch->chan) ++ goto out2; ++ ++ ppp_proto = ppp_channel_get_protocol(pch->chan); ++ if (ppp_proto == PX_PROTO_PPTP) { ++ ppp->dev->priv_flags_ext |= IFF_EXT_PPP_PPTP; ++ } else if (ppp_proto == PX_PROTO_OL2TP) { ++ version = ppp_channel_get_proto_version(pch->chan); ++ if (version == 2) ++ ppp->dev->priv_flags_ext |= IFF_EXT_PPP_L2TPV2; ++ else if (version == 3) ++ ppp->dev->priv_flags_ext |= IFF_EXT_PPP_L2TPV3; ++ } ++ notify = 1; ++ ++ out2: + ppp_unlock(ppp); + ret = 0; +- + outl: + write_unlock_bh(&pch->upl); + out: + mutex_unlock(&pn->all_ppp_mutex); ++ ++ if (notify && ppp && ppp->dev) { ++ dev_hold(ppp->dev); ++ raw_notifier_call_chain(&ppp_channel_connection_notifier_list, ++ PPP_CHANNEL_CONNECT, ppp->dev); ++ dev_put(ppp->dev); ++ } ++ + return ret; + } + +@@ -3506,6 +3556,13 @@ ppp_disconnect_channel(struct channel *p + pch->ppp = NULL; + write_unlock_bh(&pch->upl); + if (ppp) { ++ if (ppp->dev) { ++ dev_hold(ppp->dev); ++ raw_notifier_call_chain(&ppp_channel_connection_notifier_list, ++ PPP_CHANNEL_DISCONNECT, ppp->dev); ++ dev_put(ppp->dev); ++ } ++ + /* remove it from the ppp unit's list */ + ppp_lock(ppp); + list_del(&pch->clist); +@@ -3585,6 +3642,222 @@ static void *unit_find(struct idr *p, in + return idr_find(p, n); + } + ++/* Updates the PPP interface statistics. */ ++void ppp_update_stats(struct net_device *dev, unsigned long rx_packets, ++ unsigned long rx_bytes, unsigned long tx_packets, ++ unsigned long tx_bytes, unsigned long rx_errors, ++ unsigned long tx_errors, unsigned long rx_dropped, ++ unsigned long tx_dropped) ++{ ++ struct ppp *ppp; ++ ++ if (!dev) ++ return; ++ ++ if (dev->type != ARPHRD_PPP) ++ return; ++ ++ ppp = netdev_priv(dev); ++ ++ ppp_xmit_lock(ppp); ++ ppp->stats64.tx_packets += tx_packets; ++ ppp->stats64.tx_bytes += tx_bytes; ++ ppp->dev->stats.tx_errors += tx_errors; ++ ppp->dev->stats.tx_dropped += tx_dropped; ++ if (tx_packets) ++ ppp->last_xmit = jiffies; ++ ppp_xmit_unlock(ppp); ++ ++ ppp_recv_lock(ppp); ++ ppp->stats64.rx_packets += rx_packets; ++ ppp->stats64.rx_bytes += rx_bytes; ++ ppp->dev->stats.rx_errors += rx_errors; ++ ppp->dev->stats.rx_dropped += rx_dropped; ++ if (rx_packets) ++ ppp->last_recv = jiffies; ++ ppp_recv_unlock(ppp); ++} ++ ++/* Returns >0 if the device is a multilink PPP netdevice, 0 if not or < 0 if ++ * the device is not PPP. ++ */ ++int ppp_is_multilink(struct net_device *dev) ++{ ++ struct ppp *ppp; ++ unsigned int flags; ++ ++ if (!dev) ++ return -1; ++ ++ if (dev->type != ARPHRD_PPP) ++ return -1; ++ ++ ppp = netdev_priv(dev); ++ ppp_lock(ppp); ++ flags = ppp->flags; ++ ppp_unlock(ppp); ++ ++ if (flags & SC_MULTILINK) ++ return 1; ++ ++ return 0; ++} ++EXPORT_SYMBOL(ppp_is_multilink); ++ ++/* ppp_channel_get_protocol() ++ * Call this to obtain the underlying protocol of the PPP channel, ++ * e.g. PX_PROTO_OE ++ * ++ * NOTE: Some channels do not use PX sockets so the protocol value may be very ++ * different for them. ++ * NOTE: -1 indicates failure. ++ * NOTE: Once you know the channel protocol you may then either cast 'chan' to ++ * its sub-class or use the channel protocol specific API's as provided by that ++ * channel sub type. ++ */ ++int ppp_channel_get_protocol(struct ppp_channel *chan) ++{ ++ if (!chan->ops->get_channel_protocol) ++ return -1; ++ ++ return chan->ops->get_channel_protocol(chan); ++} ++EXPORT_SYMBOL(ppp_channel_get_protocol); ++ ++/* ppp_channel_get_proto_version() ++ * Call this to get channel protocol version ++ */ ++int ppp_channel_get_proto_version(struct ppp_channel *chan) ++{ ++ if (!chan->ops->get_channel_protocol_ver) ++ return -1; ++ ++ return chan->ops->get_channel_protocol_ver(chan); ++} ++EXPORT_SYMBOL(ppp_channel_get_proto_version); ++ ++/* ppp_channel_hold() ++ * Call this to hold a channel. ++ * ++ * Returns true on success or false if the hold could not happen. ++ * ++ * NOTE: chan must be protected against destruction during this call - ++ * either by correct locking etc. or because you already have an implicit ++ * or explicit hold to the channel already and this is an additional hold. ++ */ ++bool ppp_channel_hold(struct ppp_channel *chan) ++{ ++ if (!chan->ops->hold) ++ return false; ++ ++ chan->ops->hold(chan); ++ return true; ++} ++EXPORT_SYMBOL(ppp_channel_hold); ++ ++/* ppp_channel_release() ++ * Call this to release a hold you have upon a channel ++ */ ++void ppp_channel_release(struct ppp_channel *chan) ++{ ++ chan->ops->release(chan); ++} ++EXPORT_SYMBOL(ppp_channel_release); ++ ++/* Check if ppp xmit lock is on hold */ ++bool ppp_is_xmit_locked(struct net_device *dev) ++{ ++ struct ppp *ppp; ++ ++ if (!dev) ++ return false; ++ ++ if (dev->type != ARPHRD_PPP) ++ return false; ++ ++ ppp = netdev_priv(dev); ++ if (!ppp) ++ return false; ++ ++ if (spin_is_locked(&(ppp)->wlock)) ++ return true; ++ ++ return false; ++} ++EXPORT_SYMBOL(ppp_is_xmit_locked); ++ ++/* ppp_hold_channels() ++ * Returns the PPP channels of the PPP device, storing each one into ++ * channels[]. ++ * ++ * channels[] has chan_sz elements. ++ * This function returns the number of channels stored, up to chan_sz. ++ * It will return < 0 if the device is not PPP. ++ * ++ * You MUST release the channels using ppp_release_channels(). ++ */ ++int ppp_hold_channels(struct net_device *dev, struct ppp_channel *channels[], ++ unsigned int chan_sz) ++{ ++ struct ppp *ppp; ++ int c; ++ struct channel *pch; ++ ++ if (!dev) ++ return -1; ++ ++ if (dev->type != ARPHRD_PPP) ++ return -1; ++ ++ ppp = netdev_priv(dev); ++ ++ c = 0; ++ ppp_lock(ppp); ++ list_for_each_entry(pch, &ppp->channels, clist) { ++ struct ppp_channel *chan; ++ ++ if (!pch->chan) { ++ /* Channel is going / gone away */ ++ continue; ++ } ++ ++ if (c == chan_sz) { ++ /* No space to record channel */ ++ ppp_unlock(ppp); ++ return c; ++ } ++ ++ /* Hold the channel, if supported */ ++ chan = pch->chan; ++ if (!chan->ops->hold) ++ continue; ++ ++ chan->ops->hold(chan); ++ ++ /* Record the channel */ ++ channels[c++] = chan; ++ } ++ ppp_unlock(ppp); ++ return c; ++} ++EXPORT_SYMBOL(ppp_hold_channels); ++ ++/* ppp_release_channels() ++ * Releases channels ++ */ ++void ppp_release_channels(struct ppp_channel *channels[], unsigned int chan_sz) ++{ ++ unsigned int c; ++ ++ for (c = 0; c < chan_sz; ++c) { ++ struct ppp_channel *chan; ++ ++ chan = channels[c]; ++ chan->ops->release(chan); ++ } ++} ++EXPORT_SYMBOL(ppp_release_channels); ++ + /* Module/initialization stuff */ + + module_init(ppp_init); +@@ -3601,6 +3874,7 @@ EXPORT_SYMBOL(ppp_input_error); + EXPORT_SYMBOL(ppp_output_wakeup); + EXPORT_SYMBOL(ppp_register_compressor); + EXPORT_SYMBOL(ppp_unregister_compressor); ++EXPORT_SYMBOL(ppp_update_stats); + MODULE_LICENSE("GPL"); + MODULE_ALIAS_CHARDEV(PPP_MAJOR, 0); + MODULE_ALIAS_RTNL_LINK("ppp"); +--- a/drivers/net/ppp/pppoe.c ++++ b/drivers/net/ppp/pppoe.c +@@ -62,6 +62,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -87,7 +88,7 @@ + static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb); + + static const struct proto_ops pppoe_ops; +-static const struct ppp_channel_ops pppoe_chan_ops; ++static const struct pppoe_channel_ops pppoe_chan_ops; + + /* per-net private data for this module */ + static unsigned int pppoe_net_id __read_mostly; +@@ -692,7 +693,7 @@ static int pppoe_connect(struct socket * + + po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr) - 2; + po->chan.private = sk; +- po->chan.ops = &pppoe_chan_ops; ++ po->chan.ops = (struct ppp_channel_ops *)&pppoe_chan_ops; + + error = ppp_register_net_channel(dev_net(dev), &po->chan); + if (error) { +@@ -995,9 +996,80 @@ static int pppoe_fill_forward_path(struc + return 0; + } + +-static const struct ppp_channel_ops pppoe_chan_ops = { +- .start_xmit = pppoe_xmit, +- .fill_forward_path = pppoe_fill_forward_path, ++/************************************************************************ ++ * ++ * function called by generic PPP driver to hold channel ++ * ++ ***********************************************************************/ ++static void pppoe_hold_chan(struct ppp_channel *chan) ++{ ++ struct sock *sk = (struct sock *)chan->private; ++ ++ sock_hold(sk); ++} ++ ++/************************************************************************ ++ * ++ * function called by generic PPP driver to release channel ++ * ++ ***********************************************************************/ ++static void pppoe_release_chan(struct ppp_channel *chan) ++{ ++ struct sock *sk = (struct sock *)chan->private; ++ ++ sock_put(sk); ++} ++ ++/************************************************************************ ++ * ++ * function called to get the channel protocol type ++ * ++ ***********************************************************************/ ++static int pppoe_get_channel_protocol(struct ppp_channel *chan) ++{ ++ return PX_PROTO_OE; ++} ++ ++/************************************************************************ ++ * ++ * function called to get the PPPoE channel addressing ++ * NOTE: This function returns a HOLD to the netdevice ++ * ++ ***********************************************************************/ ++static int pppoe_get_addressing(struct ppp_channel *chan, ++ struct pppoe_opt *addressing) ++{ ++ struct sock *sk = (struct sock *)chan->private; ++ struct pppox_sock *po = pppox_sk(sk); ++ int err = 0; ++ ++ *addressing = po->proto.pppoe; ++ if (!addressing->dev) ++ return -ENODEV; ++ ++ dev_hold(addressing->dev); ++ return err; ++} ++ ++/* pppoe_channel_addressing_get() ++ * Return PPPoE channel specific addressing information. ++ */ ++int pppoe_channel_addressing_get(struct ppp_channel *chan, ++ struct pppoe_opt *addressing) ++{ ++ return pppoe_get_addressing(chan, addressing); ++} ++EXPORT_SYMBOL(pppoe_channel_addressing_get); ++ ++static const struct pppoe_channel_ops pppoe_chan_ops = { ++ /* PPPoE specific channel ops */ ++ .get_addressing = pppoe_get_addressing, ++ /* General ppp channel ops */ ++ .ops.start_xmit = pppoe_xmit, ++ .ops.get_channel_protocol = pppoe_get_channel_protocol, ++ .ops.hold = pppoe_hold_chan, ++ .ops.release = pppoe_release_chan, ++ .ops.fill_forward_path = pppoe_fill_forward_path, + }; + + static int pppoe_recvmsg(struct socket *sock, struct msghdr *m, +--- a/include/linux/if_pppox.h ++++ b/include/linux/if_pppox.h +@@ -93,4 +93,17 @@ enum { + PPPOX_DEAD = 16 /* dead, useless, please clean me up!*/ + }; + ++/* ++ * PPPoE Channel specific operations ++ */ ++struct pppoe_channel_ops { ++ /* Must be first - general to all PPP channels */ ++ struct ppp_channel_ops ops; ++ int (*get_addressing)(struct ppp_channel *, struct pppoe_opt *); ++}; ++ ++/* Return PPPoE channel specific addressing information */ ++extern int pppoe_channel_addressing_get(struct ppp_channel *chan, ++ struct pppoe_opt *addressing); ++ + #endif /* !(__LINUX_IF_PPPOX_H) */ +--- a/include/linux/netdevice.h ++++ b/include/linux/netdevice.h +@@ -1659,6 +1659,24 @@ enum netdev_extra_priv_flags { + IFF_NO_IP_ALIGN = 1<<0, + }; + ++ ++/** ++ * enum netdev_priv_flags_ext - &struct net_device priv_flags_ext ++ * ++ * These flags are used to check for device type and can be ++ * set and used by the drivers ++ * ++ */ ++enum netdev_priv_flags_ext { ++ IFF_EXT_TUN_TAP = 1<<0, ++ IFF_EXT_PPP_L2TPV2 = 1<<1, ++ IFF_EXT_PPP_L2TPV3 = 1<<2, ++ IFF_EXT_PPP_PPTP = 1<<3, ++ IFF_EXT_GRE_V4_TAP = 1<<4, ++ IFF_EXT_GRE_V6_TAP = 1<<5, ++ IFF_EXT_IFB = 1<<6, ++}; ++ + #define IFF_802_1Q_VLAN IFF_802_1Q_VLAN + #define IFF_EBRIDGE IFF_EBRIDGE + #define IFF_BONDING IFF_BONDING +@@ -1994,6 +2012,7 @@ struct net_device { + unsigned int flags; + unsigned int priv_flags; + unsigned int extra_priv_flags; ++ unsigned int priv_flags_ext; + const struct net_device_ops *netdev_ops; + int ifindex; + unsigned short gflags; +--- a/include/linux/ppp_channel.h ++++ b/include/linux/ppp_channel.h +@@ -19,6 +19,10 @@ + #include + #include + #include ++#include ++ ++#define PPP_CHANNEL_DISCONNECT 0 ++#define PPP_CHANNEL_CONNECT 1 + + struct ppp_channel; + +@@ -28,9 +32,19 @@ struct ppp_channel_ops { + int (*start_xmit)(struct ppp_channel *, struct sk_buff *); + /* Handle an ioctl call that has come in via /dev/ppp. */ + int (*ioctl)(struct ppp_channel *, unsigned int, unsigned long); ++ /* Get channel protocol type, one of PX_PROTO_XYZ or specific to ++ * the channel subtype ++ */ ++ int (*get_channel_protocol)(struct ppp_channel *); ++ /* Get channel protocol version */ ++ int (*get_channel_protocol_ver)(struct ppp_channel *); ++ /* Hold the channel from being destroyed */ ++ void (*hold)(struct ppp_channel *); ++ /* Release hold on the channel */ ++ void (*release)(struct ppp_channel *); + int (*fill_forward_path)(struct net_device_path_ctx *, +- struct net_device_path *, +- const struct ppp_channel *); ++ struct net_device_path *, ++ const struct ppp_channel *); + }; + + struct ppp_channel { +@@ -74,6 +88,51 @@ extern int ppp_unit_number(struct ppp_ch + /* Get the device name associated with a channel, or NULL if none */ + extern char *ppp_dev_name(struct ppp_channel *); + ++/* Call this to obtain the underlying protocol of the PPP channel, ++ * e.g. PX_PROTO_OE ++ */ ++extern int ppp_channel_get_protocol(struct ppp_channel *); ++ ++/* Call this get protocol version */ ++extern int ppp_channel_get_proto_version(struct ppp_channel *); ++ ++/* Call this to hold a channel */ ++extern bool ppp_channel_hold(struct ppp_channel *); ++ ++/* Call this to release a hold you have upon a channel */ ++extern void ppp_channel_release(struct ppp_channel *); ++ ++/* Release hold on PPP channels */ ++extern void ppp_release_channels(struct ppp_channel *channels[], ++ unsigned int chan_sz); ++ ++/* Hold PPP channels for the PPP device */ ++extern int ppp_hold_channels(struct net_device *dev, ++ struct ppp_channel *channels[], ++ unsigned int chan_sz); ++ ++/* Test if ppp xmit lock is locked */ ++extern bool ppp_is_xmit_locked(struct net_device *dev); ++ ++/* Test if the ppp device is a multi-link ppp device */ ++extern int ppp_is_multilink(struct net_device *dev); ++ ++/* Register the PPP channel connect notifier */ ++extern void ppp_channel_connection_register_notify(struct notifier_block *nb); ++ ++/* Unregister the PPP channel connect notifier */ ++extern void ppp_channel_connection_unregister_notify(struct notifier_block *nb); ++ ++/* Update statistics of the PPP net_device by incrementing related ++ * statistics field value with corresponding parameter ++ */ ++extern void ppp_update_stats(struct net_device *dev, unsigned long rx_packets, ++ unsigned long rx_bytes, unsigned long tx_packets, ++ unsigned long tx_bytes, unsigned long rx_errors, ++ unsigned long tx_errors, unsigned long rx_dropped, ++ unsigned long tx_dropped); ++ ++ + /* + * SMP locking notes: + * The channel code must ensure that when it calls ppp_unregister_channel, diff --git a/target/linux/ipq807x/patches-5.15/603-net-core-Flag-to-identify-ingress-shaping-done-for-e.patch b/target/linux/ipq807x/patches-5.15/603-net-core-Flag-to-identify-ingress-shaping-done-for-e.patch new file mode 100644 index 000000000..17fe4b206 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/603-net-core-Flag-to-identify-ingress-shaping-done-for-e.patch @@ -0,0 +1,75 @@ +From 03a9fdd468ba3635b26477ca80a1599c29341aed Mon Sep 17 00:00:00 2001 +From: Simon Casey +Date: Wed, 2 Feb 2022 19:35:35 +0100 +Subject: [PATCH] Update + 603-net-core-Flag-to-identify-ingress-shaping-done-for-e.patch for kernel + 5.15 + +Also combined with 604-net-core-Replace-nss-keyword-with-offload.patch that just changes +variable names created with 603. +--- + include/linux/skbuff.h | 2 ++ + include/net/sch_generic.h | 34 ++++++++++++++++++++++++++++++++++ + 2 files changed, 36 insertions(+) + +--- a/include/linux/skbuff.h ++++ b/include/linux/skbuff.h +@@ -652,6 +652,7 @@ typedef unsigned char *sk_buff_data_t; + * @offload_fwd_mark: Packet was L2-forwarded in hardware + * @offload_l3_fwd_mark: Packet was L3-forwarded in hardware + * @tc_skip_classify: do not classify packet. set by IFB device ++ * @tc_skip_classify_offload: do not classify packet set by offload IFB device + * @tc_at_ingress: used within tc_classify to distinguish in/egress + * @redirected: packet was redirected by packet classifier + * @from_ingress: packet was redirected from the ingress path +@@ -867,6 +868,7 @@ struct sk_buff { + #ifdef CONFIG_NET_CLS_ACT + __u8 tc_skip_classify:1; + __u8 tc_at_ingress:1; ++ __u8 tc_skip_classify_offload:1; + #endif + __u8 redirected:1; + #ifdef CONFIG_NET_REDIRECT +--- a/include/net/sch_generic.h ++++ b/include/net/sch_generic.h +@@ -739,6 +739,40 @@ static inline bool skb_skip_tc_classify( + return false; + } + ++/* ++ * Set skb classify bit field. ++ */ ++static inline void skb_set_tc_classify_offload(struct sk_buff *skb) ++{ ++#ifdef CONFIG_NET_CLS_ACT ++ skb->tc_skip_classify_offload = 1; ++#endif ++} ++ ++/* ++ * Clear skb classify bit field. ++ */ ++static inline void skb_clear_tc_classify_offload(struct sk_buff *skb) ++{ ++#ifdef CONFIG_NET_CLS_ACT ++ skb->tc_skip_classify_offload = 0; ++#endif ++} ++ ++/* ++ * Skip skb processing if sent from ifb dev. ++ */ ++static inline bool skb_skip_tc_classify_offload(struct sk_buff *skb) ++{ ++#ifdef CONFIG_NET_CLS_ACT ++ if (skb->tc_skip_classify_offload) { ++ skb_clear_tc_classify_offload(skb); ++ return true; ++ } ++#endif ++ return false; ++} ++ + /* Reset all TX qdiscs greater than index of a device. */ + static inline void qdisc_reset_all_tx_gt(struct net_device *dev, unsigned int i) + { diff --git a/target/linux/ipq807x/patches-5.15/605-5.15-qca-add-add-nss-bridge-mgr-support.patch b/target/linux/ipq807x/patches-5.15/605-5.15-qca-add-add-nss-bridge-mgr-support.patch new file mode 100644 index 000000000..4cf919974 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/605-5.15-qca-add-add-nss-bridge-mgr-support.patch @@ -0,0 +1,95 @@ +From 3c17a0e1112be70071e98d5208da5b55dcec20a6 Mon Sep 17 00:00:00 2001 +From: Simon Casey +Date: Wed, 2 Feb 2022 19:37:29 +0100 +Subject: [PATCH] Update 605-qca-add-add-nss-bridge-mgr-support.patch for + kernel 5.15 + +--- + include/linux/if_bridge.h | 4 ++++ + net/bridge/br_fdb.c | 25 +++++++++++++++++++++---- + 2 files changed, 25 insertions(+), 4 deletions(-) + +--- a/include/linux/if_bridge.h ++++ b/include/linux/if_bridge.h +@@ -236,4 +236,8 @@ typedef struct net_bridge_port *br_get_d + extern br_get_dst_hook_t __rcu *br_get_dst_hook; + /* QCA NSS ECM support - End */ + ++/* QCA NSS bridge-mgr support - Start */ ++extern struct net_device *br_fdb_bridge_dev_get_and_hold(struct net_bridge *br); ++/* QCA NSS bridge-mgr support - End */ ++ + #endif +--- a/net/bridge/br_fdb.c ++++ b/net/bridge/br_fdb.c +@@ -67,6 +67,15 @@ void br_fdb_update_unregister_notify(str + EXPORT_SYMBOL_GPL(br_fdb_update_unregister_notify); + /* QCA NSS ECM support - End */ + ++/* QCA NSS bridge-mgr support - Start */ ++struct net_device *br_fdb_bridge_dev_get_and_hold(struct net_bridge *br) ++{ ++ dev_hold(br->dev); ++ return br->dev; ++} ++EXPORT_SYMBOL_GPL(br_fdb_bridge_dev_get_and_hold); ++/* QCA NSS bridge-mgr support - End */ ++ + int __init br_fdb_init(void) + { + br_fdb_cache = kmem_cache_create("bridge_fdb_cache", +@@ -372,7 +381,7 @@ void br_fdb_cleanup(struct work_struct * + unsigned long delay = hold_time(br); + unsigned long work_delay = delay; + unsigned long now = jiffies; +- u8 mac_addr[6]; /* QCA NSS ECM support */ ++ struct br_fdb_event fdb_event; /* QCA NSS bridge-mgr support */ + + /* this part is tricky, in order to avoid blocking learning and + * consequently forwarding, we rely on rcu to delete objects with +@@ -400,12 +409,13 @@ void br_fdb_cleanup(struct work_struct * + } else { + spin_lock_bh(&br->hash_lock); + if (!hlist_unhashed(&f->fdb_node)) { +- ether_addr_copy(mac_addr, f->key.addr.addr); ++ memset(&fdb_event, 0, sizeof(fdb_event)); ++ ether_addr_copy(fdb_event.addr, f->key.addr.addr); + fdb_delete(br, f, true); + /* QCA NSS ECM support - Start */ + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, 0, +- (void *)mac_addr); ++ (void *)&fdb_event); + /* QCA NSS ECM support - End */ + } + spin_unlock_bh(&br->hash_lock); +@@ -623,6 +633,7 @@ void br_fdb_update(struct net_bridge *br + const unsigned char *addr, u16 vid, unsigned long flags) + { + struct net_bridge_fdb_entry *fdb; ++ struct br_fdb_event fdb_event; /* QCA NSS bridge-mgr support */ + + /* some users want to always flood. */ + if (hold_time(br) == 0) +@@ -648,6 +659,12 @@ void br_fdb_update(struct net_bridge *br + if (unlikely(source != READ_ONCE(fdb->dst) && + !test_bit(BR_FDB_STICKY, &fdb->flags))) { + br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH); ++ /* QCA NSS bridge-mgr support - Start */ ++ ether_addr_copy(fdb_event.addr, addr); ++ fdb_event.br = br; ++ fdb_event.orig_dev = READ_ONCE(fdb->dst->dev); ++ fdb_event.dev = source->dev; ++ /* QCA NSS bridge-mgr support - End */ + WRITE_ONCE(fdb->dst, source); + fdb_modified = true; + /* Take over HW learned entry */ +@@ -659,7 +676,7 @@ void br_fdb_update(struct net_bridge *br + /* QCA NSS ECM support - Start */ + atomic_notifier_call_chain( + &br_fdb_update_notifier_list, +- 0, (void *)addr); ++ 0, (void *)&fdb_event); + /* QCA NSS ECM support - End */ + } + diff --git a/target/linux/ipq807x/patches-5.15/606-5.15-qca-nss-ecm-bonding-add-bond_get_id.patch b/target/linux/ipq807x/patches-5.15/606-5.15-qca-nss-ecm-bonding-add-bond_get_id.patch new file mode 100644 index 000000000..a1e13b52b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/606-5.15-qca-nss-ecm-bonding-add-bond_get_id.patch @@ -0,0 +1,81 @@ +--- a/drivers/net/bonding/bond_main.c ++++ b/drivers/net/bonding/bond_main.c +@@ -249,6 +249,9 @@ static const struct flow_dissector_key f + }, + }; + ++/* QCA NSS bonding support */ ++static unsigned long bond_id_mask = 0xFFFFFFF0; ++ + static struct flow_dissector flow_keys_bonding __read_mostly; + + /*-------------------------- Forward declarations ---------------------------*/ +@@ -4060,6 +4063,23 @@ static int bond_get_lowest_level_rcu(str + } + #endif + ++/* QCA NSS bonding support */ ++int bond_get_id(struct net_device *bond_dev) ++{ ++ struct bonding *bond; ++ int bond_id = 0; ++ ++ if (!((bond_dev->priv_flags & IFF_BONDING) && ++ (bond_dev->flags & IFF_MASTER))) ++ return -EINVAL; ++ ++ bond = netdev_priv(bond_dev); ++ bond_id = bond->id; ++ ++ return bond_id; ++} ++EXPORT_SYMBOL(bond_get_id); ++ + static void bond_get_stats(struct net_device *bond_dev, + struct rtnl_link_stats64 *stats) + { +@@ -5392,6 +5412,10 @@ static void bond_destructor(struct net_d + + if (bond->rr_tx_counter) + free_percpu(bond->rr_tx_counter); ++ ++ /* QCA NSS bonding support */ ++ if (bond->id != (~0U)) ++ clear_bit(bond->id, &bond_id_mask); + } + + void bond_setup(struct net_device *bond_dev) +@@ -5969,7 +5993,14 @@ int bond_create(struct net *net, const c + + bond_work_init_all(bond); + +- rtnl_unlock(); ++ /* QCA NSS bonding support */ ++ bond->id = ~0U; ++ if (bond_id_mask != (~0UL)) { ++ bond->id = (u32)ffz(bond_id_mask); ++ set_bit(bond->id, &bond_id_mask); ++ } ++ ++ rtnl_unlock(); + return 0; + } + +--- a/include/net/bonding.h ++++ b/include/net/bonding.h +@@ -255,6 +255,7 @@ struct bonding { + spinlock_t ipsec_lock; + #endif /* CONFIG_XFRM_OFFLOAD */ + struct bpf_prog *xdp_prog; ++ u32 id; /* QCA NSS bonding */ + }; + + #define bond_slave_get_rcu(dev) \ +@@ -629,6 +630,7 @@ struct bond_net { + + int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, struct slave *slave); + netdev_tx_t bond_dev_queue_xmit(struct bonding *bond, struct sk_buff *skb, struct net_device *slave_dev); ++int bond_get_id(struct net_device *bond_dev); /* QCA NSS bonding support */ + int bond_create(struct net *net, const char *name); + int bond_create_sysfs(struct bond_net *net); + void bond_destroy_sysfs(struct bond_net *net); diff --git a/target/linux/ipq807x/patches-5.15/607-5.15-qca-nss-ecm-Add-bridge-join-and-leave-netdev-cmds.patch b/target/linux/ipq807x/patches-5.15/607-5.15-qca-nss-ecm-Add-bridge-join-and-leave-netdev-cmds.patch new file mode 100644 index 000000000..e2a1285f5 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/607-5.15-qca-nss-ecm-Add-bridge-join-and-leave-netdev-cmds.patch @@ -0,0 +1,11 @@ +--- a/net/core/dev.c ++++ b/net/core/dev.c +@@ -1685,7 +1685,7 @@ const char *netdev_cmd_to_name(enum netd + N(UDP_TUNNEL_DROP_INFO) N(CHANGE_TX_QUEUE_LEN) + N(CVLAN_FILTER_PUSH_INFO) N(CVLAN_FILTER_DROP_INFO) + N(SVLAN_FILTER_PUSH_INFO) N(SVLAN_FILTER_DROP_INFO) +- N(PRE_CHANGEADDR) ++ N(PRE_CHANGEADDR) N(BR_JOIN) N(BR_LEAVE) + } + #undef N + return "UNKNOWN_NETDEV_EVENT"; diff --git a/target/linux/ipq807x/patches-5.15/700-ipq8074-overclock-cpu-2.2ghz.patch b/target/linux/ipq807x/patches-5.15/700-ipq8074-overclock-cpu-2.2ghz.patch new file mode 100644 index 000000000..f730e42dc --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/700-ipq8074-overclock-cpu-2.2ghz.patch @@ -0,0 +1,50 @@ +--- a/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074-ac-cpu.dtsi +@@ -32,16 +32,37 @@ + compatible = "operating-points-v2"; + opp-shared; + +- opp-1017600000 { +- opp-hz = /bits/ 64 <1017600000>; +- opp-microvolt = <1>; +- clock-latency-ns = <200000>; +- }; +- opp-1382400000 { +- opp-hz = /bits/ 64 <1382400000>; +- opp-microvolt = <2>; +- clock-latency-ns = <200000>; +- }; ++ opp-1017600000 { ++ opp-hz = /bits/ 64 <1017600000>; ++ opp-microvolt = <1>; ++ clock-latency-ns = <100000>; ++ }; ++ opp-1382400000 { ++ opp-hz = /bits/ 64 <1382400000>; ++ opp-microvolt = <2>; ++ clock-latency-ns = <100000>; ++ }; ++ opp-1651100000 { ++ opp-hz = /bits/ 64 <1651100000>; ++ opp-microvolt = <3>; ++ clock-latency-ns = <100000>; ++ }; ++ opp-1843100000 { ++ opp-hz = /bits/ 64 <1843100000>; ++ opp-microvolt = <4>; ++ clock-latency-ns = <100000>; ++ }; ++ opp-1910000000 { ++ opp-hz = /bits/ 64 <1910000000>; ++ opp-microvolt = <5>; ++ clock-latency-ns = <100000>; ++ }; ++ opp-2208000000 { ++ opp-hz = /bits/ 64 <2208000000>; ++ opp-microvolt = <6>; ++ clock-latency-ns = <100000>; ++ }; ++ + }; + }; + diff --git a/target/linux/ipq807x/patches-5.15/900-arm64-dts-add-OpenWrt-DTS-files.patch b/target/linux/ipq807x/patches-5.15/900-arm64-dts-add-OpenWrt-DTS-files.patch new file mode 100644 index 000000000..6f4f64e64 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/900-arm64-dts-add-OpenWrt-DTS-files.patch @@ -0,0 +1,25 @@ +From 8aec79b6d4d59616eb6ce4fbfb94658b3e79d9ce Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 11 May 2021 13:29:33 +0200 +Subject: [PATCH] arm64: dts: add OpenWrt DTS files + +Lets add custom OpenWrt DTS files to Makefile. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/Makefile | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/arm64/boot/dts/qcom/Makefile ++++ b/arch/arm64/boot/dts/qcom/Makefile +@@ -4,6 +4,10 @@ dtb-$(CONFIG_ARCH_QCOM) += apq8094-sony- + dtb-$(CONFIG_ARCH_QCOM) += apq8096-db820c.dtb + dtb-$(CONFIG_ARCH_QCOM) += apq8096-ifc6640.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq6018-cp01-c1.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq8071-mf269.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq8071-ax6.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq8071-ax3600.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq8072-ax9000.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk01.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk10-c1.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk10-c2.dtb diff --git a/target/linux/ipq807x/patches-5.15/901-arm64-dts-ipq8074-add-label-to-clocks.patch b/target/linux/ipq807x/patches-5.15/901-arm64-dts-ipq8074-add-label-to-clocks.patch new file mode 100644 index 000000000..642349a3b --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/901-arm64-dts-ipq8074-add-label-to-clocks.patch @@ -0,0 +1,24 @@ +From 4bdbb8ddf49ebca198719940c4b6246f90a4251d Mon Sep 17 00:00:00 2001 +From: Robert Marko +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 +--- + 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 +@@ -12,7 +12,7 @@ + model = "Qualcomm Technologies, Inc. IPQ8074"; + compatible = "qcom,ipq8074"; + +- clocks { ++ clocks: clocks { + sleep_clk: sleep_clk { + compatible = "fixed-clock"; + clock-frequency = <32768>; diff --git a/target/linux/ipq807x/patches-5.15/902-5.15-arm64-provide-dma-cache-routines-with-same-API-as-32.patch b/target/linux/ipq807x/patches-5.15/902-5.15-arm64-provide-dma-cache-routines-with-same-API-as-32.patch new file mode 100644 index 000000000..b5f7e2367 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/902-5.15-arm64-provide-dma-cache-routines-with-same-API-as-32.patch @@ -0,0 +1,29 @@ +From a2f32cec773113a9d0a80f0b20947da9bc29f3bd Mon Sep 17 00:00:00 2001 +From: Simon Casey +Date: Wed, 2 Feb 2022 20:17:21 +0100 +Subject: [PATCH] Update + 902-arm64-provide-dma-cache-routines-with-same-API-as-32.patch for kernel + 5.15 + +--- + arch/arm64/include/asm/cacheflush.h | 9 +++++++++ + 1 file changed, 9 insertions(+) + +--- a/arch/arm64/include/asm/cacheflush.h ++++ b/arch/arm64/include/asm/cacheflush.h +@@ -110,6 +110,15 @@ static inline void flush_icache_range(un + extern void __dma_map_area(const void *, size_t, int); + extern void __dma_unmap_area(const void *, size_t, int); + extern void __dma_flush_area(const void *, size_t); ++extern void __dma_inv_area(const void *start, const void *end); ++extern void __dma_clean_area(const void *start, const void *end); ++ ++#define dmac_flush_range(start, end) \ ++ __dma_flush_area(start, (void *)(end) - (void *)(start)) ++#define dmac_inv_range(start, end) \ ++ __dma_inv_area(start, end) ++#define dmac_clean_range(start, end) \ ++ __dma_clean_area(start, end) + + /* + * Copy user data from/to a page which is mapped into a different diff --git a/target/linux/ipq807x/patches-5.15/903-5.15-arm64-mm-export-__dma_inv_area-and-__dma_clean_area.patch b/target/linux/ipq807x/patches-5.15/903-5.15-arm64-mm-export-__dma_inv_area-and-__dma_clean_area.patch new file mode 100644 index 000000000..e96e9bd70 --- /dev/null +++ b/target/linux/ipq807x/patches-5.15/903-5.15-arm64-mm-export-__dma_inv_area-and-__dma_clean_area.patch @@ -0,0 +1,56 @@ +From 6fae2dc7f45d14b5341e9929b5c5fef60596ed4f Mon Sep 17 00:00:00 2001 +From: Simon Casey +Date: Wed, 2 Feb 2022 20:25:38 +0100 +Subject: [PATCH] Update + 903-arm64-mm-export-__dma_inv_area-and-__dma_clean_area.patch for kernel 5.15 + +--- + arch/arm64/include/asm/cacheflush.h | 4 ++-- + arch/arm64/mm/cache.S | 4 ++-- + arch/arm64/mm/flush.c | 2 ++ + 3 files changed, 6 insertions(+), 4 deletions(-) + +--- a/arch/arm64/include/asm/cacheflush.h ++++ b/arch/arm64/include/asm/cacheflush.h +@@ -110,8 +110,8 @@ static inline void flush_icache_range(un + extern void __dma_map_area(const void *, size_t, int); + extern void __dma_unmap_area(const void *, size_t, int); + extern void __dma_flush_area(const void *, size_t); +-extern void __dma_inv_area(const void *start, const void *end); +-extern void __dma_clean_area(const void *start, const void *end); ++extern void __dma_inv_area(const void *, const void *); ++extern void __dma_clean_area(const void *, const void *); + + #define dmac_flush_range(start, end) \ + __dma_flush_area(start, (void *)(end) - (void *)(start)) +--- a/arch/arm64/mm/cache.S ++++ b/arch/arm64/mm/cache.S +@@ -140,7 +140,7 @@ SYM_FUNC_END(dcache_clean_pou) + * - start - kernel start address of region + * - end - kernel end address of region + */ +-SYM_FUNC_START_LOCAL(__dma_inv_area) ++SYM_FUNC_START(__dma_inv_area) + SYM_FUNC_START_PI(dcache_inval_poc) + /* FALLTHROUGH */ + +@@ -178,7 +178,7 @@ SYM_FUNC_END(__dma_inv_area) + * - start - virtual start address of region + * - end - virtual end address of region + */ +-SYM_FUNC_START_LOCAL(__dma_clean_area) ++SYM_FUNC_START(__dma_clean_area) + SYM_FUNC_START_PI(dcache_clean_poc) + /* FALLTHROUGH */ + +--- a/arch/arm64/mm/flush.c ++++ b/arch/arm64/mm/flush.c +@@ -77,6 +77,8 @@ EXPORT_SYMBOL(flush_dcache_page); + * Additional functions defined in assembly. + */ + EXPORT_SYMBOL(caches_clean_inval_pou); ++EXPORT_SYMBOL(__dma_inv_area); ++EXPORT_SYMBOL(__dma_clean_area); + + #ifdef CONFIG_ARCH_HAS_PMEM_API + void arch_wb_cache_pmem(void *addr, size_t size)