From ea54bd7ca12e9a80cb9e4ff0a850ba66e7c637c0 Mon Sep 17 00:00:00 2001 From: lean Date: Mon, 12 Sep 2022 04:29:38 +0800 Subject: [PATCH] ipq60xx: add Qihoo 360v6 AX1800 support --- package/kernel/linux/modules/usb.mk | 2 +- package/qca/nss/qca-nss-clients-64/Makefile | 6 +- target/linux/ipq60xx/Makefile | 15 +- .../ipq60xx/base-files/etc/board.d/02_network | 45 + .../etc/hotplug.d/firmware/11-ath11k-caldata | 29 + .../lib/firmware/IPQ6018/hw1.0/board.bin | 1 + .../lib/firmware/ath11k/IPQ6018/bdwlan.bin | 1 + .../lib/firmware/ath11k/IPQ6018/bdwlan_CA.bin | Bin 0 -> 65536 bytes .../lib/firmware/ath11k/IPQ6018/bdwlan_EU.bin | 1 + .../lib/firmware/ath11k/IPQ6018/bdwlan_JP.bin | 1 + .../lib/firmware/ath11k/IPQ6018/bdwlan_US.bin | Bin 0 -> 65536 bytes .../lib/firmware/ath11k/IPQ6018/board.bin | 1 + .../firmware/ath11k/IPQ6018/hw1.0/board.bin | 1 + .../ipq60xx/{config-5.10 => config-5.15} | 121 +- .../qcom/ipq6018-eap610-outdoor-switch.dtsi | 331 + .../boot/dts/qcom/ipq6018-eap610-outdoor.dts | 122 + .../arch/arm64/boot/dts/qcom/ipq6018-ess.dtsi | 135 + .../arm64/boot/dts/qcom/ipq6018-qihoo-v6.dts | 88 + .../boot/dts/qcom/ipq6018-upstreamable.dtsi | 120 + target/linux/ipq60xx/generic/target.mk | 1 + target/linux/ipq60xx/image/Makefile | 19 +- target/linux/ipq60xx/image/generic.mk | 40 + ...-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch | 48 + ...pdate-BAM-DMA-node-name-per-DT-schem.patch | 32 + ...4-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch | 45 + ...sm8996-Move-clock-cells-to-QMP-PHY-c.patch | 58 + ...qcom-Correct-QMP-PHY-child-node-name.patch | 50 + ...-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch | 99 + ...-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch | 41 + ...-Support-reserved-memory-description.patch | 172 + ...64-dts-qcom-ipq8074-add-SMEM-support.patch | 56 + ...ipq8074-add-the-reserved-memory-node.patch | 35 + ...om-ipq8074-enable-the-GICv2m-support.patch | 41 + ...pq8074-drop-the-clock-frequency-prop.patch | 31 + ...lign-dmas-in-I2C-SPI-UART-with-DT-sc.patch | 67 + ...lign-clocks-in-I2C-SPI-with-DT-schem.patch | 74 + ...orrect-DWC3-node-names-and-unit-addr.patch | 42 + ...pq8074-add-dedicated-qcom-ipq8074-dw.patch | 41 + ...align-DWC3-USB-clocks-with-DT-schema.patch | 44 + ...64-dts-qcom-adjust-whitespace-around.patch | 41 + ...ts-qcom-Fix-sdhci-node-names-use-mmc.patch | 39 + ...ix-ordering-of-clocks-clock-names-fo.patch | 52 + ...ck-qcom-ipq8074-add-PPE-crypto-clock.patch | 30 + ...lk-qcom-ipq8074-add-PPE-crypto-clock.patch | 57 + ...ngs-clock-qcom-ipq8074-add-USB-GDSCs.patch | 30 + ...-v6.0-clk-qcom-ipq8074-add-USB-GDSCs.patch | 86 + ...s-qcom-ipq8074-add-USB-power-domains.patch | 48 + ...pq8074-move-ARMv8-timer-out-of-SoC-n.patch | 56 + ...-dts-qcom-ipq8074-add-reset-to-SDHCI.patch | 32 + ...com-ipq8074-drop-USB-PHY-clock-index.patch | 41 + ...arm64-dts-qcom-ipq8074-add-APCS-node.patch | 42 + ...pq8074-add-size-address-cells-to-DTS.patch | 64 + ...ipq8074-add-interrupt-parent-to-DTSI.patch | 59 + ...or-qcom_spmi-add-support-for-HT_P150.patch | 63 + ...or-qcom_spmi-add-support-for-HT_P600.patch | 64 + ...pmi-add-support-for-PMP8074-regulato.patch | 74 + ...om-pmic-gpio-add-support-for-PMP8074.patch | 30 + ...-pmic-Sort-compatibles-in-the-driver.patch | 65 + ...ic-Add-missing-PMICs-supported-by-so.patch | 71 + ...i-adc5-add-ADC5_VREF_VADC-to-rev2-AD.patch | 32 + ...ie-make-pipe-clock-rate-configurable.patch | 52 + ...e-add-IPQ8074-PCIe-Gen3-QMP-PHY-supp.patch | 206 + ...qcom-socinfo-Add-IPQ8074-family-ID-s.patch | 55 + ...ve-GEN3_RELATED-DBI-register-to-comm.patch | 52 + ...slot-capabilities-using-PCI_EXP_SLTC.patch | 51 + ...44-v6.0-PCI-qcom-Add-IPQ60xx-support.patch | 219 + ...ts-ipq8074-add-reserved-memory-nodes.patch | 75 + ...1-clk-qcom-clk-rcg2-add-rcg2-mux-ops.patch | 56 + ...apss-ipq6018-fix-apcs_alias0_clk_src.patch | 66 + ...q6018-mark-apcs_alias0_core_clk-as-c.patch | 36 + ...q-pll-use-OF-match-data-for-Alpha-PL.patch | 73 + ...q-pll-update-IPQ6018-Alpha-PLL-confi.patch | 43 + ...apss-ipq-pll-add-support-for-IPQ8074.patch | 56 + ...pq8074-correct-APCS-register-space-s.patch | 36 + ...64-dts-qcom-ipq8074-add-A53-PLL-node.patch | 35 + ...s-ipc-add-IPQ8074-APSS-clock-support.patch | 55 + ...-dts-qcom-ipq8074-add-clocks-to-APCS.patch | 34 + ...11-PCI-qcom-add-IPQ8074-Gen3-support.patch | 50 + ...dts-ipq8074-fix-PCI-related-DT-nodes.patch | 221 + ...remoteproc-qcom-Add-PRNG-proxy-clock.patch | 160 + ...moteproc-qcom-Add-secure-PIL-support.patch | 148 + ...Add-support-for-split-q6-m3-wlan-fir.patch | 109 + ...oc-qcom-Add-ssr-subdevice-identifier.patch | 29 + ...Update-regmap-offsets-for-halt-regis.patch | 85 + ...tsens-Add-support-for-combined-inter.patch | 153 + ...tsens-allow-configuring-min-and-max-.patch | 115 + ...rs-thermal-tsens-add-IPQ8074-support.patch | 81 + ...-arm64-dts-ipq8074-add-thermal-nodes.patch | 136 + ...om-spmi-pmic-add-support-for-PMP8074.patch | 30 + ...0123-arm64-dts-qcom-add-PMP8074-DTSI.patch | 164 + ...s-qcom-ipq8074-hk01-add-VQMMC-supply.patch | 40 + ...0125-arm64-dts-ipq8074-add-CPU-clock.patch | 64 + ...-arm64-dts-ipq8074-add-label-to-cpus.patch | 30 + ...q8074-add-cooling-cells-to-CPU-nodes.patch | 53 + ...ngs-clock-qcom-Add-reset-for-WCSSAON.patch | 31 + .../0129-clk-qcom-Add-WCSSAON-reset.patch | 30 + ...c-wcss-disable-auto-boot-for-IPQ8074.patch | 52 + ...com-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch | 125 + ...0132-arm64-dts-ipq8074-Add-WLAN-node.patch | 140 + ...-support-for-resetting-multiple-bits.patch | 63 + ...pq8074-add-missing-networking-resets.patch | 70 + ...rm64-dts-ipq8074-add-label-to-clocks.patch | 29 + .../0136-power-Add-Qualcomm-APM.patch | 1066 ++ ...egulator-add-Qualcomm-CPR-regulators.patch | 12171 ++++++++++++++++ ...1000-arm64-dts-add-OpenWrt-DTS-files.patch | 30 + ...com-ipq6018-Add-mdio-bus-description.patch | 40 + ...om-add-support-for-hw-controlled-RCG.patch | 150 + ...qcom-ipq6018-add-missing-clock-flags.patch | 152 + ...-clk-qcom-ipq6018-add-missing-clocks.patch | 750 + ...m-ipq6018-update-Huayra-PLL-settings.patch | 61 + ...-openwrt-update-parent-mapping-for-p.patch | 67 + ...com-ipq6018-Add-missing-fixed-clocks.patch | 53 + ...ss-Add-ipq6018-support-in-remoteproc.patch | 65 + ...c-Add-non-secure-Q6-bringup-sequence.patch | 472 + ...-rproc-Add-non-secure-Q6-bringup-seq.patch | 60 + ...prinkle-pointless-debug-print-statem.patch | 189 + 116 files changed, 21775 insertions(+), 91 deletions(-) create mode 100644 target/linux/ipq60xx/base-files/etc/board.d/02_network create mode 100644 target/linux/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata create mode 120000 target/linux/ipq60xx/base-files/lib/firmware/IPQ6018/hw1.0/board.bin create mode 120000 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan.bin create mode 100755 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan_CA.bin create mode 120000 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan_EU.bin create mode 120000 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan_JP.bin create mode 100755 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan_US.bin create mode 120000 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/board.bin create mode 120000 target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/hw1.0/board.bin rename target/linux/ipq60xx/{config-5.10 => config-5.15} (88%) create mode 100644 target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor-switch.dtsi create mode 100644 target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts create mode 100644 target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-ess.dtsi create mode 100644 target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-qihoo-v6.dts create mode 100644 target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-upstreamable.dtsi create mode 100644 target/linux/ipq60xx/generic/target.mk create mode 100644 target/linux/ipq60xx/image/generic.mk create mode 100644 target/linux/ipq60xx/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0002-v5.16-arm64-dts-qcom-Update-BAM-DMA-node-name-per-DT-schem.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0003-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0004-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0005-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0006-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0007-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0009-v5.18-arm64-dts-qcom-ipq8074-add-SMEM-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0010-v5.18-arm64-dts-qcom-ipq8074-add-the-reserved-memory-node.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0011-v5.18-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0012-v5.18-arm64-dts-qcom-ipq8074-drop-the-clock-frequency-prop.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0013-v5.19-arm64-dts-qcom-align-dmas-in-I2C-SPI-UART-with-DT-sc.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0014-v5.19-arm64-dts-qcom-align-clocks-in-I2C-SPI-with-DT-schem.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0015-v5.19-arm64-dts-qcom-correct-DWC3-node-names-and-unit-addr.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0016-v5.19-arm64-dts-qcom-ipq8074-add-dedicated-qcom-ipq8074-dw.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0017-v5.19-arm64-dts-qcom-align-DWC3-USB-clocks-with-DT-schema.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0018-v6.0-arm64-dts-qcom-adjust-whitespace-around.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0019-v6.0-arm64-dts-qcom-Fix-sdhci-node-names-use-mmc.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0020-v6.0-arm64-dts-qcom-Fix-ordering-of-clocks-clock-names-fo.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0021-v6.0-dt-bindings-clock-qcom-ipq8074-add-PPE-crypto-clock.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0022-v6.0-clk-qcom-ipq8074-add-PPE-crypto-clock.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0023-v6.0-dt-bindings-clock-qcom-ipq8074-add-USB-GDSCs.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0024-v6.0-clk-qcom-ipq8074-add-USB-GDSCs.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0025-v6.0-arm64-dts-qcom-ipq8074-add-USB-power-domains.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0026-v6.0-arm64-dts-qcom-ipq8074-move-ARMv8-timer-out-of-SoC-n.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0027-v6.0-arm64-dts-qcom-ipq8074-add-reset-to-SDHCI.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0028-v6.0-arm64-dts-qcom-ipq8074-drop-USB-PHY-clock-index.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0029-v6.0-arm64-dts-qcom-ipq8074-add-APCS-node.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0030-v6.0-arm64-dts-qcom-ipq8074-add-size-address-cells-to-DTS.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0031-v6.0-arm64-dts-qcom-ipq8074-add-interrupt-parent-to-DTSI.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0032-v6.0-regulator-qcom_spmi-add-support-for-HT_P150.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0033-v6.0-regulator-qcom_spmi-add-support-for-HT_P600.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0034-v6.0-regulator-qcom_spmi-add-support-for-PMP8074-regulato.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0035-v6.0-pinctrl-qcom-pmic-gpio-add-support-for-PMP8074.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0036-v5.16-mfd-qcom-spmi-pmic-Sort-compatibles-in-the-driver.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0037-v5.16-mfd-qcom-spmi-pmic-Add-missing-PMICs-supported-by-so.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0038-v6.0-iio-adc-qcom-spmi-adc5-add-ADC5_VREF_VADC-to-rev2-AD.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0039-v6.0-phy-qcom-qmp-pcie-make-pipe-clock-rate-configurable.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0040-v6.0-phy-qcom-qmp-pcie-add-IPQ8074-PCIe-Gen3-QMP-PHY-supp.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0041-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0042-v6.0-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0043-v6.0-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0044-v6.0-PCI-qcom-Add-IPQ60xx-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0100-arm64-dts-ipq8074-add-reserved-memory-nodes.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0101-clk-qcom-clk-rcg2-add-rcg2-mux-ops.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0102-clk-qcom-apss-ipq6018-fix-apcs_alias0_clk_src.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0103-clk-qcom-apss-ipq6018-mark-apcs_alias0_core_clk-as-c.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0104-clk-qcom-apss-ipq-pll-use-OF-match-data-for-Alpha-PL.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0105-clk-qcom-apss-ipq-pll-update-IPQ6018-Alpha-PLL-confi.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0106-clk-qcom-apss-ipq-pll-add-support-for-IPQ8074.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0107-arm64-dts-qcom-ipq8074-correct-APCS-register-space-s.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0108-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0109-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0110-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0111-PCI-qcom-add-IPQ8074-Gen3-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0112-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0113-remoteproc-qcom-Add-PRNG-proxy-clock.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0114-remoteproc-qcom-Add-secure-PIL-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0115-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0116-remoteproc-qcom-Add-ssr-subdevice-identifier.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0117-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0118-drivers-thermal-tsens-Add-support-for-combined-inter.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0119-drivers-thermal-tsens-allow-configuring-min-and-max-.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0120-drivers-thermal-tsens-add-IPQ8074-support.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0121-arm64-dts-ipq8074-add-thermal-nodes.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0122-mfd-qcom-spmi-pmic-add-support-for-PMP8074.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0123-arm64-dts-qcom-add-PMP8074-DTSI.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0124-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0125-arm64-dts-ipq8074-add-CPU-clock.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0126-arm64-dts-ipq8074-add-label-to-cpus.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0127-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0128-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0129-clk-qcom-Add-WCSSAON-reset.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0130-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0131-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0132-arm64-dts-ipq8074-Add-WLAN-node.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0133-clk-ipq-support-for-resetting-multiple-bits.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0134-clk-qcom-ipq8074-add-missing-networking-resets.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0135-arm64-dts-ipq8074-add-label-to-clocks.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0136-power-Add-Qualcomm-APM.patch create mode 100644 target/linux/ipq60xx/patches-5.15/0137-regulator-add-Qualcomm-CPR-regulators.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1000-arm64-dts-add-OpenWrt-DTS-files.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1001-arm64-dts-qcom-ipq6018-Add-mdio-bus-description.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1002-clk-qcom-add-support-for-hw-controlled-RCG.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1003-clk-qcom-ipq6018-add-missing-clock-flags.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1004-clk-qcom-ipq6018-add-missing-clocks.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1005-clk-qcom-ipq6018-update-Huayra-PLL-settings.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1006-clk-qcom-ipq6018-openwrt-update-parent-mapping-for-p.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1007-arm64-dts-qcom-ipq6018-Add-missing-fixed-clocks.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1008-remoteproc-wcss-Add-ipq6018-support-in-remoteproc.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1009-ipq6018-rproc-Add-non-secure-Q6-bringup-sequence.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1010-Simplify-ipq6018-rproc-Add-non-secure-Q6-bringup-seq.patch create mode 100644 target/linux/ipq60xx/patches-5.15/1011-HACK-q6v5_wcss-Sprinkle-pointless-debug-print-statem.patch diff --git a/package/kernel/linux/modules/usb.mk b/package/kernel/linux/modules/usb.mk index dbb553802..2e216ca69 100644 --- a/package/kernel/linux/modules/usb.mk +++ b/package/kernel/linux/modules/usb.mk @@ -477,7 +477,7 @@ $(eval $(call KernelPackage,usb-dwc3)) define KernelPackage/usb-dwc3-qcom TITLE:=DWC3 Qualcomm USB driver - DEPENDS:=@(TARGET_ipq40xx||TARGET_ipq806x||TARGET_ipq807x) +kmod-usb-dwc3 + DEPENDS:=@(TARGET_ipq40xx||TARGET_ipq806x||TARGET_ipq807x||TARGET_ipq60xx) +kmod-usb-dwc3 KCONFIG:= CONFIG_USB_DWC3_QCOM FILES:= $(LINUX_DIR)/drivers/usb/dwc3/dwc3-qcom.ko AUTOLOAD:=$(call AutoLoad,53,dwc3-qcom,1) diff --git a/package/qca/nss/qca-nss-clients-64/Makefile b/package/qca/nss/qca-nss-clients-64/Makefile index 741646ba8..153166872 100644 --- a/package/qca/nss/qca-nss-clients-64/Makefile +++ b/package/qca/nss/qca-nss-clients-64/Makefile @@ -17,7 +17,7 @@ define KernelPackage/qca-nss-drv-pppoe-64 CATEGORY:=Kernel modules SUBMENU:=Network Devices TITLE:=Kernel driver for NSS (connection manager) - PPPoE - DEPENDS:=@TARGET_ipq807x +kmod-qca-nss-drv-64 +kmod-ppp +kmod-pppoe + DEPENDS:=@(TARGET_ipq60xx||TARGET_ipq807x) +kmod-qca-nss-drv-64 +kmod-ppp +kmod-pppoe FILES:=$(PKG_BUILD_DIR)/pppoe/qca-nss-pppoe.ko AUTOLOAD:=$(call AutoLoad,51,qca-nss-pppoe) endef @@ -31,7 +31,7 @@ define KernelPackage/qca-nss-drv-bridge-mgr-64 CATEGORY:=Kernel modules SUBMENU:=Network Devices TITLE:=Kernel driver for NSS bridge manager - DEPENDS:=@LINUX_5_10 @TARGET_ipq807x +kmod-qca-nss-drv-64 +kmod-qca-nss-drv-vlan-mgr-64 + DEPENDS:=@(LINUX_5_10||LINUX_5_15) @(TARGET_ipq60xx||TARGET_ipq807x) +kmod-qca-nss-drv-64 +kmod-qca-nss-drv-vlan-mgr-64 FILES:=$(PKG_BUILD_DIR)/bridge/qca-nss-bridge-mgr.ko AUTOLOAD:=$(call AutoLoad,51,qca-nss-bridge-mgr) endef @@ -45,7 +45,7 @@ define KernelPackage/qca-nss-drv-vlan-mgr-64 CATEGORY:=Kernel modules SUBMENU:=Network Devices TITLE:=Kernel driver for NSS vlan manager - DEPENDS:=@LINUX_5_10 @TARGET_ipq807x +kmod-qca-nss-drv-64 + DEPENDS:=@(LINUX_5_10||LINUX_5_15) @(TARGET_ipq60xx||TARGET_ipq807x) +kmod-qca-nss-drv-64 FILES:=$(PKG_BUILD_DIR)/vlan/qca-nss-vlan.ko AUTOLOAD:=$(call AutoLoad,51,qca-nss-vlan) endef diff --git a/target/linux/ipq60xx/Makefile b/target/linux/ipq60xx/Makefile index e84d92e68..8aeb9605b 100644 --- a/target/linux/ipq60xx/Makefile +++ b/target/linux/ipq60xx/Makefile @@ -3,15 +3,20 @@ include $(TOPDIR)/rules.mk ARCH:=aarch64 BOARD:=ipq60xx BOARDNAME:=Qualcomm Atheros IPQ60xx -FEATURES:=squashfs ramdisk fpu nand source-only +FEATURES:=squashfs ramdisk fpu nand rtc emmc KERNELNAME:=Image dtbs -CPU_TYPE:=cortex-a53+crypto +CPU_TYPE:=cortex-a53 SUBTARGETS:=generic -KERNEL_PATCHVER:=5.10 +KERNEL_PATCHVER:=5.15 include $(INCLUDE_DIR)/target.mk DEFAULT_PACKAGES += \ - kmod-leds-gpio kmod-gpio-button-hotplug - + kmod-usb3 kmod-usb-dwc3 kmod-usb-dwc3-qcom \ + kmod-leds-gpio kmod-gpio-button-hotplug \ + kmod-ath11k-ahb wpad-openssl uboot-envtools \ + kmod-qca-nss-dp kmod-qca-nss-drv-64 nss-firmware-ipq6018 \ + kmod-qca-nss-drv-pppoe-64 kmod-qca-nss-ecm-64 \ + kmod-qca-nss-drv-bridge-mgr-64 kmod-qca-nss-drv-vlan-mgr-64 + $(eval $(call BuildTarget)) diff --git a/target/linux/ipq60xx/base-files/etc/board.d/02_network b/target/linux/ipq60xx/base-files/etc/board.d/02_network new file mode 100644 index 000000000..364ecf0b0 --- /dev/null +++ b/target/linux/ipq60xx/base-files/etc/board.d/02_network @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright (c) 2015 The Linux Foundation. All rights reserved. +# Copyright (c) 2011-2015 OpenWrt.org +# + +. /lib/functions/uci-defaults.sh +. /lib/functions/system.sh + +ipq60xx_setup_interfaces() +{ + local board="$1" + + case "$board" in + qihoo,v6) + ucidef_set_interfaces_lan_wan "eth1 eth2 eth3" "eth0" + ;; + *) + echo "Unsupported hardware. Network interfaces not initialized" + ;; + esac +} + +ipq60xx_setup_macs() +{ + local board="$1" + + case "$board" in + qihoo,v6) + lan_mac=$(mtd_get_mac_ascii factory lanMac) + wan_mac=$(macaddr_add "$lan_mac" 1) + ;; + esac + + [ -n "$lan_mac" ] && ucidef_set_interface_macaddr "lan" $lan_mac + [ -n "$wan_mac" ] && ucidef_set_interface_macaddr "wan" $wan_mac +} + +board_config_update +board=$(board_name) +ipq60xx_setup_interfaces $board +ipq60xx_setup_macs $board +board_config_flush + +exit 0 \ No newline at end of file diff --git a/target/linux/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata b/target/linux/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata new file mode 100644 index 000000000..df5149d8f --- /dev/null +++ b/target/linux/ipq60xx/base-files/etc/hotplug.d/firmware/11-ath11k-caldata @@ -0,0 +1,29 @@ +#!/bin/sh + +[ -e /lib/firmware/$FIRMWARE ] && exit 0 + +. /lib/functions/caldata.sh + +board=$(board_name) + +mount_factory_data() { + local firmware_name=$1 + local part + . /lib/functions/system.sh + + + part=$(find_mtd_chardev "factory_data") + ubiattach -p "$part" + mkdir /tmp/facdata + mount -t ubifs,ro ubi0:ubi_factory_data /tmp/facdata + cp /tmp/facdata/radio "/lib/firmware/$firmware_name" +} + +case "$FIRMWARE" in +"ath11k/IPQ6018/hw1.0/cal-ahb-c000000.wifi.bin") + mount_factory_data $FIRMWARE + ;; +*) + exit 1 + ;; +esac diff --git a/target/linux/ipq60xx/base-files/lib/firmware/IPQ6018/hw1.0/board.bin b/target/linux/ipq60xx/base-files/lib/firmware/IPQ6018/hw1.0/board.bin new file mode 120000 index 000000000..c1e9a5da4 --- /dev/null +++ b/target/linux/ipq60xx/base-files/lib/firmware/IPQ6018/hw1.0/board.bin @@ -0,0 +1 @@ +../bdwlan_US.bin \ No newline at end of file diff --git a/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan.bin b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan.bin new file mode 120000 index 000000000..fceba4ebf --- /dev/null +++ b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan.bin @@ -0,0 +1 @@ +/tmp/IPQ6018/bdwlan.bin \ No newline at end of file diff --git a/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan_CA.bin b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/bdwlan_CA.bin new file mode 100755 index 0000000000000000000000000000000000000000..baad53443d6a0416fa45171465561769c2580528 GIT binary patch literal 65536 zcmeHQYjj&xcHZ}1*|8Jj_#r=(IF4UQl#mIo2?-d71Om=KJIQpjY@q#>mlo|)321sYZtYs##$RtE+$bak!%$Qqaq(^B01-G_Ab zvL)Lxb~x9cr1PD#&pvy9=iDpl%F>m{Vj*>V{g;wN%J!Uix}{ zHBnZfQ6ec&;=+Dra!8H2Rg^;&$dR(8>!h2cwbB}?T3XGe)zS^pI=)}Y^;J@qRHXl{ z+b9;IX*>#8s7Qal4e!m*zGH5Z^cf$|M_;SOV|tnX2JvViOTXjS8NVAf8SAL6 zGqcg<@pW(P^Y;(#KYaAW&{NNxK6CE;%Rl@18yCaUky6vs(`TzlOS7b=&0?CK#+6}q z22i5bt5W%mT18Tuvm0QzjqbF~l*S#fTx=dPqcvXfagiI>ZwL)+E++_?k>0hA|huFc1m|1-`Nh z4DI*taNK3@v$=y33_a$5#PNXrHd}|OZohxKV<5ur&mH%(ozG;q*T2p2E&DAte*QGJ zp~L=Nj{EF>Tf0$1H1vJ{cT_umo;J3jL;jtPd+gt^Z7^!sZZF&2Y2Rcs+Z|NxHiy{3 zUo1OM(4QzxOo4$t!@(CamxHH$hlhvh__1RXb1r(%SOr|(?#=y!2Tu&2K70P<*De|_ z%ma_*&l4@=*D*y~i3<6+VqHm;Q_7!u<`ZQ``HUo7i1O!%N*?3aLl1L*K8{%VZAQ)l z?w`y3`89q#n;OHT9K%CI1zz^CN{^v{eV9Jfi{-2Q9OrO|WB3loz{fl_2R=QVmB;;K zoP`eeRmOQNJi;-sj{@Xz1NWcLaoE^Ldbo8YFaKNaj~rMBwf}0ZKR!Lq410$b*;}g# zi2->ah9b>R=ImI#g7s>Do{K;XYqb8EIUK{BpxtPtGS;&A0Q=a+c_0S(;Q7qVj?ahM zpWo{R`mZzfziPCRgKZHyBp5@*^SmZ`E*9{5D&+N)cOB2;LXHDE1cw>5RCbo<)2yXF z7x8LY!RtYdf%hw0*Z5*k*Y#o668i<#q`szdcx}hCrYd+%mmK2xi^ z%P)dY7o!Ogm``KBSj+t}KD5jjt*6#j&N`~c2YoKAMhv;!KhEQWx*ni|v2S565Cigv zW50;dVI}J@PhSrs@#zql3nK>11#6ju{d?BfIs{_i{&-!6xv<)~e?IsBzxZOPILB*} zb-=OK*2(=>>GN5@KH@meE#m&A+`pLT0yePqUQ zHZ&rJD$Zj`Vlk-u7UzNeJFsu@T&OX`v8EP1$2uIEm=1yc8!=$tTB*+kbQpU%&>}uQ zls?KaU@nchfDU=wKhAwKj$x8+GW6H}#~goQzu)%F;7~(D{(X)e_HWs4HPt=Mc8}Qa zv-wSRC;fXJ+wFJSHks;R7w+$*|ND+@_CK|4G}#UN4?4bUzuWe8Q{4~z-*Y@iRyTgc>-}^o5KkC?Hf6#WjsqRPq;}LdREDzXjGui#b|Ab?oeVgr0Q{A)vV~+3H zzisOe*2P=$Zaz4C_O&PnanpMs!`Fd*`}XeLOLXYq!2<{O8@1w5C?FIN3J3*+0zv_y zfKWgvAQTV^2nB=!LII(GP(Uak6c7ps1%v`Z0il3UU?wP#PJ`+v;*upLZFWZH+^p=J zTx(u_L7{El{GtWLf&YJ+HO5AiO8K;yR#6l6QXkzx1N0#6phsvg9i|iX6n{_SIeLLE z&_D6tNW|X-dWQCMpY>EspN_m4@j?HP-(k0@f>tc5Fu-5>kS_O9n*QC6%&Z(!=nG3q z%gQSjEnZ?@y6o!ZU*k;1NP$1u&!Q5#hE|h##8&KD3p>C$Nw5mZB&yx+HA7$e#qaC+Vl8n?VDM#?B2eq(g2s*z}R{1xP+37A1A z_&;`uy0uMz^te>&aYsC^Z)&JB+Ja>l*E(w=>)oy|$HqLaschuwF~_TwlP2&CajZ@M zOrLl2yqe~n*KuO&&}?rmd&D>VX2^OLVO|*uh~tv^_!Fz_@o3Mz=Hp*vIi3|E^p2&m zxnAhqo$zz2c|Dr7jH9eA%I@wcuiMP)KA!b0Y=i8rq1 z^g#)tQ~0+SB$guvGJeu#ddg9pd)6X1e=r1;KjKndL9%CR1j+K9~QzAW9qkbYWYmsR8vEnz| zl$3y(@6D}RS(ar*l|4K!JzzPN+c`fuJU^p7joUd-IV{hF@?C~lmYeP)<22XAle^j8 zJeykC+!|SCLHPG)c^SsgL}5)7)(_W(wtQ)|af^&f zTeHYr%RkZrxf!>}*6M0wPLlN)ZOrl1B`dx}{EjE?WcEoU_GH;6Gr#fpPF9|Y_#IE& z$?TIz?80_p3M6wL!+lRI(#h!^9@Cf2Cnu}Ky(ByD#3LGik3@4B|48xmOf;TY_VJCD zm{qK?%=L+ROpdiVp6O6dPL_#zoenyfV@NFe zp88mGWUi0LPb`~cpQmv@|Bn&-i+k^n@&tJC!j{ zfo<#Y@7uB*_|DyKQ#ekEMGZ|Ky|Ib2f zLII(GP(Ua!xeCmsPx)#(ViO7o1%v`Z0il3UKqw#-5DEwdgaSeVp@2|8C?FIN3J3*+ z0#}X#S!7AOat(`& zOrnEi#F&hXgOK|JO5ux9xS^6v{g_!IXVC^JI3>~_|g`}yhsv>m07sMZC5ouDqNbpv3-=+_N^odRnE zz+M9L0#I;=#H<-W+p9c6lNSK{2WGWB0N7h#y#Uz1Fso|;z<$B2qOKJHdl#$?0DF(e ztFHyr8BjrB9z&5g%rVaqCk6Cj&0Opr^sM!sG z^-G=9Tn9kU0a$JT&~`6aJplHgw2GQt0NBH5^8sMHq)W8E6#&~WIca?x0Jc|BsHGR+ z1xbgc4b)N(pzXMH3$=Iwu#-GOOBVoknn!ST0$@*bel8~fb_#9n0NC?j%>dYWFdqQ+ z0$2wC_6mDxZ3DoTD66Tp1ps@UEnB+)us0)f4+W~-O$RCm01At}mk=E@pSPqW6ZUAk|=pV0$|-> zJpkB7uzCQj53C6Q>*w)2E&yx`m=6HEALDreu&we^+TZ}dwy~wR9st`3)&_umSH6+F z9RS!PJf61;02^dWrwaf(B;QIcZUF2tX0BQQ>YXPvkd3|=Z z12~qS;qrSQfWA>H)Ad?4{8O zfHf)k)X@%r^(v**(F1^O;b-rV^2MRu6vav@Qa=B9sf}hSmncp7n18HvHO=O|ZH`jOM}QmYD&BuH zmBlnyvGd-$gtC>Tl*7l6T&06@Q|tM-&`H)*C*>(OQ@*l^3Y1&;=x{69ls1~juPx>) zy;P)h(E_E1ij@IcsC+;rv+knO)Zb8<@)4CQJEoZ;(Cxn|$+=J?%DHn)b(hyPcM%_w(%zzHQ;#%$c2;8|misXVUF_O=u#m#7{eV zsX{HCclN z7$wA~)nN>y)hGpfAZD!E(> z66Q^-)4Y6=ZzW`Tdp=5cAD|ll3%_CCRA@DVf*7Id2-bgm4SZvd-5H&~A<|i=TlxU!%L)KZWg= z{*hJ%dkdw8^oPH%-Jp-h*;(kSf?~tJ%2NZplE31skR`KBGp9;91r}Ji>V|fi(s){Q z&MVnMmYfB;ze-u~GZVwee@$7U~X= zpF-({Mm{ynCH7!!HT=`}m@m8!!3NH)7Fn+WmuQLB$oC`MU#5)fjS6suGKvBfMuT`1 z3J3*+0z!eWfC72^IUBZh>(<3}%jeJ6rg|%&&!>9GX*FIUZBbvQt{9ovpK%TOiFsKO zxE6gZKgql<{ZvehjW&i1JFY-oP77fqfkI7AQTV^d<7L)Ku!18>hkvy`6tjFjvwk+#qdu$w|3S)z9kVbD=qtSRYdSZj(OhBbB5#|{~ zY{zk_ohp8VA2CKKl!D`xxZqNUr&F4wK+Wo6rF50G8VERKb*=vJ3Zx+|McvgQZTwf_=N`?C0 zI)0oN&^4XL^3?i~NRm>6T}qN1B%&k~Fcgr?Dz6r(2isE`sQW0XwG>j^6OW5FNC zupe*pX;Vjtkw}iPH@QDSfB5tdpQ17en?mWPu9DC}jF3CusD6vyzVNR*|GoM@=+`lf zMyWogZKGI-rtv6Xp+f!nHr$(^eaGA+=~F(QkG@um#*8xk4dT&4mVVE#Gk!nnU~Hqd z&df%a$Jf2F&)+|I;K;F)Lr*?^=IpuiFaPvsuU`xsj+B~~mNr|3)oMw#&SIKo<;pO- zanRmz_z0J{6iA^9>hp!fYn%51`~|jXb%91v5DH8a1yZT|3wg~mO@=ShNTLF>=!_a2 z2ge;FBO^PCwC#6z$$(oK(cHOZd@or;(~zVB)uBw?=6<3mii}%jEQe6{aWX3nR={;& z+umctXEWh{^$TX;1-etE2rl0!PqgP=CGLk`R(GA=WsYohwF3C?FL0(kd`? zz`xUZm!r?_4oWcesQ+Q-{f^u09j3Yi{vFPN2)n;<-p6)6lifc5cIP)7x7hjl)7XZN z_;)++b@=V=Mh(%>_x#^d?f7}x*oF@KcRBBNe9gYWsA0Q(YVuyIK z>^#AEqBJoD2KEjIU&vezp7tFc9;Oq=k54SP@Sd>>xV+t)`v(u596s~x`Ile2XuL2F zJeECAw2)uN6mBCb;NyyIB~f+>f9jb>lo91Kl5io)o+B!LlwS`$#QuC7vGLoC>;>$f z!~XmlKb}p^;StW^L85#w$5^H3kk2tpAL_-5Rd$ZYaF}!W7U#gnJar6wdN?ze{bQVk z4v$sJ<5+lquVqckGWbunwyKYRw;?9%qETLyPp*YC>|r zIFLi37AIqNEU#d_>d$i#$YG7#2a(Q|@&w z%W_^1Y7V?#*}KM*o+)<*gwwWgSsA|gRyU6E|3Gp5yyTJ zp~FhnVV=GoM&i>UFc(G+m!u~}(7kJI5=D_DMvTZz;IL3fcMjY=kpL~4C>ua88AmrsHyQdH|D(>oblhkEda$dZA^(2oPRBRwx0>pnV!MYO_uBoY zx>Npr&K-_B?VC(>unUiO%Kts*cE_LFH=68*{fC_2acs4J)l~O=|971aI_|Ldn(BTK z)$XsH4>|rU#O_J|Vdrkg-S*8UUn5?RsVw|1sxY#{>4;O?5x?pNO#2a=G7ro5}9S{>PpB9oy}9n(ChMA9sG&@lAVw zurA(`ck`j)XJ3nQ5I4OCGGZOvzklDpeME;39XfdMfKe+Rg#tnWp@2|8C?FIN3J3*+ z0zv_yfKWgvAQTV^2nB=!LII(GP(Uak6c7ps1!jT*X*8&QA}(1{th3WI=4NJP=h$-d z@(b+q<`*s~3jF`mtT8sCRLY~pw2GRjm-^@q8lVSgCp}F2=m?#pC;59C&(RBXf&PjA zMk4+$(9?8)eb!SEeKPV!#0ULDey78(3R=FX+yH;+L%Q5cR{gsj8JXFp)E5?)l$Mn* zTD-)ublKI*zrrIKV+j1oekK*uHME+XJc>5z;c@t>UmwX{I!GsIm`>B*(s_E7enuDR zZF-kJppW#?oaGTbKpv`~0*>^*ql{tsvSp5PZBxgvG%$vlux6s>csXkA45{RQk}-o$1tASM@6BOlsNv^aB8!f+}>uBh4(}Le$?K_&S>AvihV!1JfkCzM*s8m4IX>)?8~o@ZdExF&5plX9(>5m?lNd1Px>x;25_iHK}`W7}q0il3UKqw#-5DEwdgaSeVp}_Q2;3|5* z7}+WOTMUxSzpad)S5~y^lt`&eQkl=vxs|z0H~jQ7CmQ;T4V)u!j-=-p$bxg_AMBJ!&()}(NX}X&+I_72%{Ilw zVCH*st5%j}Sy5#VAD5o6oXYJyJ~@1RM!S{Ud7N_CI1?K0GUT${bRU_gxh9^mo9)fB zsg=#Gk!2Qye}9&jVGKzpj9X-DRh2O($$E^|=XmOpm0u!p$CGz5eGa>-*{ptYn+M19Z%lL z^hqRlVLLGek~xpzu_u=4 zMHM4fy>`|_TN$y+@vNItlXI+cJaLN3%L8Vy$^nB>3dug_G2Qf!B}J^cFxSWOji)}= z9GUCmi4)5v+2?88oxh0KU)=kC6j$Anz?zR%iZ!Cx^}#ubR*LNxt0g!G(MqwrV%J;v zoSB8snOS0-GqdnHGYg+Hv&1-OX5n*Y7CvWYiE+-%yZN<%?%qSe6Tjy2VpVhVee8VE zqA6*_89D5;;$$kJ$mXC$}hgCmeu+fwXZG)f{dI^x5@l}7Ge_$ z2nB=!LV?LuU@m>aSJM%jP(Uak6c7ps1%v`Z0il3UKqw#-5DEwdgaSeVp@2|8C?FKL zaumoUi}lJGjTnhgKqw#-5DEwdgaU~ukVRV(F`uw)HveP@g1j&H>A%VGdBn9|Cfvy+ z21rJpIrN!=6pP)ViJqSm$^{b&2nDV@1#Dy~#HcK|T2=$#7$Uxk6*CtAmd;lKt5X4% zNo&a24uBnz*|He`E9Pr#IlTbbwR{CFXDi^~|7*#GWfy?9aw;Qd2LR?ke?9B<38`U}iusuB5mL33XkXda5;Bc_k?B}Zk&~}VIq*@mMc9OcN)(wCS!>=0vI}O$b zfV~9f1)$(giCHs%wpY1_CNBW?kIZU&0I)Z~dI7M1VOG}yfc=tJMO`ZZ_6}GZ0QN5T zS6>ULGp4Mf`bGe4zoIN^?*!2HpB%Hk9zfe~!F&MNe=}=n2EhIY%nN{h&aANk0F$NF z%BrFc$!}Qd&cebpY7)X!8JI z>(JH#fK{W-2Y@w6+|UDnx!AJF2Y~rFmnJs=)(zGIfNkU`O&tJOAG79m0L(A-P_r8V z>z6vIxekDy1F+lxpzR*8dI0PJX%#iQ0I-M9<^#ZXOP6SUD*$#ts-g950N6fBp_X2N z7bG2#Hc(4FfVLCTE!5%#z)o=wEnNWE8ScT=34lGt<8##jV5ia64uCxm)(n812lD}7 zFMxFbV6SkL);0iaiL#nnTL7@1vt?@+0QQDtr`8?->@DeLYV8ET-hrhL0Be%hP-_za z_Ac5s0ARlY^8#S+gLMF4Kjt;q;{(7hNmbO^4S+Rr%!XC~?04|11Hk?ZtQG*fj1jp3 zu#eGJ4}g8jdbTwpIXaz1%^b767c7{XAU&SeyJ2d7J=Pr@WRtjR06T zSPuZU5v(2n>jP^7!1}pAj|%|X0_Fq2?n8fG0BoDQlr}g4uB2@vc?5~9hPsU7B>L)C^J_r0CtouTN?nd6U;oV0N5#JowWeiR$iZ7 z?EsGDr@5`&383vq@+E3_17K&l&D#!uy~3HbHv(X~#WQzvQ<1 zb^z=WEWH5O0X`jJ=P6fgOD0kF@wKVK67Mm#=W zBY^Rx^D~~$2cXTyM}40g04snc2hwsWgk>#&wqj--jR07=auanp0kBMNbJhc3YdA_{ z4FJ}pV{B91-v0FHzI%$oIf0chJJ?V1vwId)pkPf{y?<(p9|w zW+;nkuHxXmcL`-FODUU=AvsD1<)qg0aiNoJsWp_V+)R1OCdyZC;iJQ?WLMf~9>2Dj zuk=!((nSlD9x74>Xrb~x70YDArOJm?rtG5fl;gC>vWFI>{2MJ!d5@N)^wHAU zU*+3ozI}}xX(L9iWRw@XzLN%=pnHsG|iR)yoj(bx}$jVAvb2sN9 z7o&vyv^w-*v>K(L2XfY0k@MDEH%HN0Q3}@0*ISL=(5m%@(!7AL7Vn*0kKT%smH|o_ z+t%EFQ0vfJb|A{)KyN5%Q9jr0vhz0QY4NlY;%OzsyFKqsZiS{=39VWQ`e`Ms4Wmxh zOPDvUPK)w!o{ccd+w)M;qI}GegQX1B19e*DzYnzT&vU35yqWi&?)zRKN^q@d^9FT; zS#iiKXya#}RQ+_kIx39^zjj)|wGzz^jJTRhGNoFL3UK~OZG-X|BXCO?4_TV+WR+HH4zRb-oOz4Z1v%@n+^JDL zz9r)too8C0S6zLB!8seY&t7ffDWv2qtJFH=`J)uFMuT`1 z3J3*+0z!c=fdaYwIUBZh>(<3}%j3`2rh3b#&!)P|Y1Ll=ZBbvQt{9ovpK%TOiA7lv zxE6h^ILW*&{Zvehoi>Ja^fB%|4hHlct}Kqw#-5DEwdCSQR<>JFY-oc!=2qEJ96AQTV^d$k literal 0 HcmV?d00001 diff --git a/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/board.bin b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/board.bin new file mode 120000 index 000000000..c2822c631 --- /dev/null +++ b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/board.bin @@ -0,0 +1 @@ +bdwlan_US.bin \ No newline at end of file diff --git a/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/hw1.0/board.bin b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/hw1.0/board.bin new file mode 120000 index 000000000..c1e9a5da4 --- /dev/null +++ b/target/linux/ipq60xx/base-files/lib/firmware/ath11k/IPQ6018/hw1.0/board.bin @@ -0,0 +1 @@ +../bdwlan_US.bin \ No newline at end of file diff --git a/target/linux/ipq60xx/config-5.10 b/target/linux/ipq60xx/config-5.15 similarity index 88% rename from target/linux/ipq60xx/config-5.10 rename to target/linux/ipq60xx/config-5.15 index f3745359d..2e4c9a102 100644 --- a/target/linux/ipq60xx/config-5.10 +++ b/target/linux/ipq60xx/config-5.15 @@ -5,49 +5,34 @@ 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=11 CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=11 CONFIG_ARCH_PROC_KCORE_TEXT=y CONFIG_ARCH_QCOM=y -CONFIG_ARCH_RANDOM=y -CONFIG_ARCH_SELECT_MEMORY_MODEL=y -CONFIG_ARCH_SPARSEMEM_DEFAULT=y CONFIG_ARCH_SPARSEMEM_ENABLE=y CONFIG_ARCH_STACKWALK=y CONFIG_ARCH_SUSPEND_POSSIBLE=y -CONFIG_ARCH_USES_HIGH_VMA_FLAGS=y +CONFIG_ARCH_WANTS_NO_INSTR=y CONFIG_ARM64=y CONFIG_ARM64_4K_PAGES=y -CONFIG_ARM64_AMU_EXTN=y -CONFIG_ARM64_BTI=y -CONFIG_ARM64_BTI_KERNEL=y -CONFIG_ARM64_CNP=y CONFIG_ARM64_CRYPTO=y -CONFIG_ARM64_E0PD=y CONFIG_ARM64_ERRATUM_1165522=y CONFIG_ARM64_ERRATUM_1286807=y -CONFIG_ARM64_ERRATUM_1418040=y -CONFIG_ARM64_HW_AFDBM=y -CONFIG_ARM64_MODULE_PLTS=y -CONFIG_ARM64_MTE=y +CONFIG_ARM64_LD_HAS_FIX_ERRATUM_843419=y CONFIG_ARM64_PAGE_SHIFT=12 -CONFIG_ARM64_PAN=y CONFIG_ARM64_PA_BITS=48 CONFIG_ARM64_PA_BITS_48=y CONFIG_ARM64_PTR_AUTH=y -CONFIG_ARM64_RAS_EXTN=y +CONFIG_ARM64_PTR_AUTH_KERNEL=y CONFIG_ARM64_SVE=y CONFIG_ARM64_TAGGED_ADDR_ABI=y -CONFIG_ARM64_TLB_RANGE=y CONFIG_ARM64_VA_BITS=39 CONFIG_ARM64_VA_BITS_39=y -CONFIG_ARM64_VHE=y CONFIG_ARM64_WORKAROUND_REPEAT_TLBI=y CONFIG_ARM64_WORKAROUND_SPECULATIVE_AT=y -# CONFIG_ARMV8_DEPRECATED is not set CONFIG_ARM_AMBA=y CONFIG_ARM_ARCH_TIMER=y CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y @@ -57,53 +42,52 @@ 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_ARM_SMCCC_SOC_ID=y CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_SD=y CONFIG_BLK_MQ_PCI=y CONFIG_BLK_MQ_VIRTIO=y CONFIG_BLK_PM=y -CONFIG_BLOCK_COMPAT=y CONFIG_CAVIUM_TX2_ERRATUM_219=y CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y -CONFIG_CLKDEV_LOOKUP=y CONFIG_CLONE_BACKWARDS=y CONFIG_COMMON_CLK=y CONFIG_COMMON_CLK_QCOM=y -CONFIG_COMPAT=y # CONFIG_COMPAT_32BIT_TIME is not set -CONFIG_COMPAT_BINFMT_ELF=y -CONFIG_COMPAT_NETLINK_MESSAGES=y -CONFIG_COMPAT_OLD_SIGACTION=y CONFIG_COREDUMP=y CONFIG_CPUFREQ_DT=y CONFIG_CPUFREQ_DT_PLATDEV=y CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=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_COMMON=y # CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set -CONFIG_CPU_FREQ_GOV_ONDEMAND=y +# 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_LADDER=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_AUTHENC=y CONFIG_CRYPTO_CBC=y CONFIG_CRYPTO_DEFLATE=y CONFIG_CRYPTO_DEV_QCE=y +CONFIG_CRYPTO_DEV_QCE_AEAD=y +# CONFIG_CRYPTO_DEV_QCE_ENABLE_AEAD is not set CONFIG_CRYPTO_DEV_QCE_ENABLE_ALL=y # CONFIG_CRYPTO_DEV_QCE_ENABLE_SHA is not set # CONFIG_CRYPTO_DEV_QCE_ENABLE_SKCIPHER is not set @@ -112,13 +96,12 @@ CONFIG_CRYPTO_DEV_QCE_SKCIPHER=y CONFIG_CRYPTO_DEV_QCE_SW_MAX_LEN=512 CONFIG_CRYPTO_DEV_QCOM_RNG=y CONFIG_CRYPTO_ECB=y -CONFIG_CRYPTO_GF128MUL=y CONFIG_CRYPTO_HASH_INFO=y CONFIG_CRYPTO_HW=y +CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y CONFIG_CRYPTO_LIB_DES=y CONFIG_CRYPTO_LIB_SHA256=y CONFIG_CRYPTO_LZO=y -CONFIG_CRYPTO_NULL2=y CONFIG_CRYPTO_RNG=y CONFIG_CRYPTO_RNG2=y CONFIG_CRYPTO_SHA1=y @@ -140,6 +123,7 @@ 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 @@ -151,12 +135,13 @@ 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_MULTI_HANDLER=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 @@ -172,29 +157,26 @@ 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_HOLES_IN_ZONE=y CONFIG_HWSPINLOCK=y CONFIG_HWSPINLOCK_QCOM=y -CONFIG_HZ=250 -# CONFIG_HZ_100 is not set -CONFIG_HZ_250=y -CONFIG_HZ_PERIODIC=y CONFIG_I2C=y CONFIG_I2C_BOARDINFO=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_HELPER_AUTO=y # CONFIG_I2C_QCOM_CCI is not set CONFIG_I2C_QUP=y +CONFIG_IIO=y CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 CONFIG_INITRAMFS_SOURCE="" CONFIG_IPQ_APSS_6018=y # CONFIG_IPQ_APSS_8074 is not set -# CONFIG_IPQ_APSS_PLL is not set +CONFIG_IPQ_APSS_PLL=y # CONFIG_IPQ_GCC_4019 is not set CONFIG_IPQ_GCC_6018=y # CONFIG_IPQ_GCC_806X is not set @@ -208,7 +190,6 @@ 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_LZO_COMPRESS=y @@ -226,8 +207,6 @@ CONFIG_MEMFD_CREATE=y # CONFIG_MFD_QCOM_RPM is not set CONFIG_MFD_SPMI_PMIC=y CONFIG_MFD_SYSCON=y -CONFIG_MHI_BUS=y -CONFIG_MHI_BUS_DEBUG=y CONFIG_MIGRATION=y CONFIG_MMC=y CONFIG_MMC_BLOCK=y @@ -253,7 +232,6 @@ CONFIG_MODULES_USE_ELF_RELA=y # CONFIG_MSM_MMCC_8974 is not set # CONFIG_MSM_MMCC_8996 is not set # CONFIG_MSM_MMCC_8998 is not set -CONFIG_MTD_CMDLINE_PARTS=y CONFIG_MTD_NAND_CORE=y CONFIG_MTD_NAND_ECC=y CONFIG_MTD_NAND_ECC_SW_HAMMING=y @@ -261,9 +239,6 @@ CONFIG_MTD_NAND_QCOM=y CONFIG_MTD_QCOMSMEM_PARTS=y CONFIG_MTD_RAW_NAND=y CONFIG_MTD_SPI_NOR=y -CONFIG_MTD_SPLIT_FIRMWARE=y -CONFIG_MTD_SPLIT_FIT_FW=y -CONFIG_MTD_SPLIT_UIMAGE_FW=y CONFIG_MTD_UBI=y CONFIG_MTD_UBI_BEB_LIMIT=20 CONFIG_MTD_UBI_BLOCK=y @@ -272,9 +247,13 @@ 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_NR_CPUS=256 +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 @@ -286,11 +265,10 @@ CONFIG_OF_GPIO=y CONFIG_OF_IRQ=y CONFIG_OF_KOBJ=y CONFIG_OF_MDIO=y -CONFIG_OF_NET=y -CONFIG_OLD_SIGSUSPEND3=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 @@ -335,7 +313,7 @@ CONFIG_PINCTRL_MSM=y # 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_SPMI_PMIC=y # CONFIG_PINCTRL_QCOM_SSBI_PMIC is not set # CONFIG_PINCTRL_QCS404 is not set # CONFIG_PINCTRL_SC7180 is not set @@ -348,21 +326,24 @@ CONFIG_PM=y CONFIG_PM_CLK=y CONFIG_PM_OPP=y CONFIG_POWER_RESET=y -CONFIG_POWER_RESET_MSM=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=y +# 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 @@ -379,7 +360,6 @@ CONFIG_QCOM_Q6V5_COMMON=y # CONFIG_QCOM_Q6V5_PAS is not set CONFIG_QCOM_Q6V5_WCSS=y CONFIG_QCOM_QFPROM=y -CONFIG_QCOM_QMI_HELPERS=y # CONFIG_QCOM_RMTFS_MEM is not set # CONFIG_QCOM_RPMH is not set CONFIG_QCOM_RPROC_COMMON=y @@ -391,28 +371,27 @@ CONFIG_QCOM_SMEM_STATE=y CONFIG_QCOM_SMP2P=y # CONFIG_QCOM_SMSM is not set CONFIG_QCOM_SOCINFO=y +CONFIG_QCOM_SPMI_ADC5=y # CONFIG_QCOM_SYSMON is not set CONFIG_QCOM_TSENS=y +CONFIG_QCOM_VADC_COMMON=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_QRTR=y -CONFIG_QRTR_MHI=y -CONFIG_QRTR_SMD=y -CONFIG_QRTR_TUN=y CONFIG_QUEUED_RWLOCKS=y CONFIG_QUEUED_SPINLOCKS=y -CONFIG_RANDOMIZE_BASE=y -CONFIG_RANDOMIZE_MODULE_REGION_FULL=y +CONFIG_RAS=y CONFIG_RATIONAL=y CONFIG_REGMAP=y CONFIG_REGMAP_MMIO=y CONFIG_REGMAP_SPMI=y CONFIG_REGULATOR=y -CONFIG_REGULATOR_FIXED_VOLTAGE=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 @@ -427,14 +406,24 @@ 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_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y +CONFIG_RTC_I2C_AND_SPI=y CONFIG_RWSEM_SPIN_ON_OWNER=y +# CONFIG_SCHED_CORE is not set CONFIG_SCHED_MC=y +CONFIG_SCHED_SMT=y CONFIG_SCHED_THERMAL_PRESSURE=y +CONFIG_SCSI=y +CONFIG_SCSI_COMMON=y +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_SCSI_PROC_FS is not set # CONFIG_SC_DISPCC_7180 is not set # CONFIG_SC_GCC_7180 is not set # CONFIG_SC_GPUCC_7180 is not set @@ -453,6 +442,7 @@ CONFIG_SERIAL_MCTRL_GPIO=y CONFIG_SERIAL_MSM=y CONFIG_SERIAL_MSM_CONSOLE=y CONFIG_SGL_ALLOC=y +CONFIG_SG_POOL=y CONFIG_SMP=y # CONFIG_SM_GCC_8150 is not set # CONFIG_SM_GCC_8250 is not set @@ -460,7 +450,11 @@ CONFIG_SMP=y # 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 @@ -475,13 +469,10 @@ CONFIG_SRCU=y CONFIG_SWIOTLB=y CONFIG_SWPHY=y CONFIG_SYSCTL_EXCEPTION_TRACE=y -CONFIG_SYSVIPC_COMPAT=y -CONFIG_SYS_SUPPORTS_HUGETLBFS=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_NETLINK=y CONFIG_THERMAL_OF=y CONFIG_THREAD_INFO_IN_TASK=y CONFIG_TICK_CPU_ACCOUNTING=y @@ -490,6 +481,8 @@ 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 @@ -500,6 +493,7 @@ CONFIG_VIRTIO=y CONFIG_VMAP_STACK=y CONFIG_WANT_DEV_COREDUMP=y CONFIG_WATCHDOG_CORE=y +CONFIG_WATCHDOG_SYSFS=y CONFIG_XPS=y CONFIG_XXHASH=y CONFIG_ZLIB_DEFLATE=y @@ -507,4 +501,3 @@ CONFIG_ZLIB_INFLATE=y CONFIG_ZONE_DMA32=y CONFIG_ZSTD_COMPRESS=y CONFIG_ZSTD_DECOMPRESS=y -CONFIG_IPQ_MEM_PROFILE=0 \ No newline at end of file diff --git a/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor-switch.dtsi b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor-switch.dtsi new file mode 100644 index 000000000..90d3b335a --- /dev/null +++ b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor-switch.dtsi @@ -0,0 +1,331 @@ +#include "ipq6018-ess.dtsi" + +&switch { + status = "okay"; + + switch_cpu_bmp = <0x01>; + switch_lan_bmp = <0x20>; + switch_wan_bmp = <0x20>; + switch_inner_bmp = <0xc0>; + switch_mac_mode = <0xff>; + switch_mac_mode1 = <0x0f>; + switch_mac_mode2 = <0xff>; + bm_tick_mode = <0x00>; + tm_tick_mode = <0x00>; + + 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>; + }; + }; + }; + }; + + qcom,port_phyinfo { + port@4 { + port_id = <0x05>; + phy_address = <0x04>; + port_mac_sel = "QGMAC_PORT"; + }; + }; +}; diff --git a/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts new file mode 100644 index 000000000..f651944b2 --- /dev/null +++ b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-eap610-outdoor.dts @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: (GPL-2.0+) + +/dts-v1/; + +#include "ipq6018.dtsi" +#include "ipq6018-upstreamable.dtsi" +#include "ipq6018-eap610-outdoor-switch.dtsi" + +#include +#include + +/ { + model = "TP-Link EAP610-Outdoor"; + compatible = "tplink,eap610-outdoor", "qcom,ipq6018"; + + aliases { + serial0 = &blsp1_uart3; + led-boot = &led_power; + led-failsafe = &led_power; + led-running = &led_power; + led-upgrade = &led_power; + }; + + chosen { + stdout-path = "serial0:115200n8"; + }; + + keys { + compatible = "gpio-keys"; + + reset { + label = "reset"; + gpios = <&tlmm 9 GPIO_ACTIVE_LOW>; + linux,code = ; + }; + }; + + leds { + compatible = "gpio-leds"; + + led_power: led-1 { + label = "green:power"; + gpios = <&tlmm 36 GPIO_ACTIVE_HIGH>; + }; + }; +}; + +&blsp1_uart3 { + pinctrl-0 = <&serial_3_pins>; + pinctrl-names = "default"; + status = "okay"; +}; + +&tlmm { + mdio_pins: mdio-pins { + mdc { + pins = "gpio64"; + function = "mdc"; + drive-strength = <8>; + bias-pull-up; + }; + + mdio { + pins = "gpio65"; + function = "mdio"; + drive-strength = <8>; + bias-pull-up; + }; + }; +}; + +&soc { + dp5: dp@5 { + compatible = "qcom,nss-dp"; + reg = <0x0 0x3a001800 0x0 0x200>; + qcom,mactype = <0>; + qcom,id = <5>; + + phy-handle = <&rtl8211f_4>; + phy-mode = "sgmii"; + }; +}; + +&edma { + status = "okay"; +}; + +&mdio { + status = "okay"; + pinctrl-0 = <&mdio_pins>; + pinctrl-names = "default"; + phy-reset-gpio = <&tlmm 0x4b GPIO_ACTIVE_HIGH>; + + rtl8211f_4: ethernet-phy@4 { + reg = <4>; + reset-gpios = <&tlmm 77 GPIO_ACTIVE_LOW>; + }; +}; + +&switch { + status = "okay"; +}; + +&qpic_bam { + status = "okay"; +}; + +&qpic_nand { + status = "okay"; + + nand@0 { + reg = <0>; + + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + }; +}; + +&wifi { + status = "okay"; +}; diff --git a/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-ess.dtsi b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-ess.dtsi new file mode 100644 index 000000000..51d4bc2bc --- /dev/null +++ b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-ess.dtsi @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: (GPL-2.0+) + +&soc { + switch: ess-switch@3a000000 { + compatible = "qcom,ess-switch-ipq60xx"; + reg = <0x3a000000 0x1000000>; + switch_access_mode = "local bus"; + mdio-bus = <&mdio>; + status = "disabled"; + + clocks = <&gcc 0xb7>, + <&gcc 0xb8>, + <&gcc 0x8d>, + <&gcc 0x98>, + <&gcc 0x99>, + <&gcc 0x9c>, + <&gcc 0x7b>, + <&gcc 0x7c>, + <&gcc 0x7d>, + <&gcc 0x7e>, + <&gcc 0x7f>, + <&gcc 0x70>, + <&gcc 0x6f>, + <&gcc 0x77>, + <&gcc 0x76>, + <&gcc 0x63>, + <&gcc 0x62>, + <&gcc 0x71>, + <&gcc 0x5b>, + <&gcc 0x64>, + <&gcc 0xae>, + <&gcc 0x60>, + <&gcc 0x72>, + <&gcc 0x65>, + <&gcc 0x66>, + <&gcc 0x67>, + <&gcc 0x68>, + <&gcc 0x69>, + <&gcc 0x6a>, + <&gcc 0x6b>, + <&gcc 0x6c>, + <&gcc 0x6d>, + <&gcc 0x6e>, + <&gcc 0x8e>, + <&gcc 0x8f>, + <&gcc 0x90>, + <&gcc 0x91>, + <&gcc 0x92>, + <&gcc 0x93>, + <&gcc 0x94>, + <&gcc 0x95>, + <&gcc 0x96>, + <&gcc 0x97>, + <&gcc 0x9a>, + <&gcc 0x9b>, + <&gcc 0x0c>, + <&gcc 0x0d>, + <&gcc 0xf2>; + clock-names = "cmn_ahb_clk", "cmn_sys_clk", "uniphy0_ahb_clk", + "uniphy0_sys_clk", "uniphy1_ahb_clk", + "uniphy1_sys_clk", "port1_mac_clk", + "port2_mac_clk", "port3_mac_clk", "port4_mac_clk", + "port5_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", "gcc_mdio_ahb_clk", + "gcc_nss_noc_clk", "gcc_nssnoc_snoc_clk", + "gcc_nss_crypto_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", + "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", + "nss_port5_rx_clk_src", "nss_port5_tx_clk_src", + "gcc_snoc_nssnoc_clk"; + 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_NSSPORT1_RESET>, + <&gcc GCC_NSSPORT2_RESET>, + <&gcc GCC_NSSPORT3_RESET>, + <&gcc GCC_NSSPORT4_RESET>, + <&gcc GCC_NSSPORT5_RESET>, + <&gcc GCC_UNIPHY0_PORT1_ARES>, + <&gcc GCC_UNIPHY0_PORT2_ARES>, + <&gcc GCC_UNIPHY0_PORT3_ARES>, + <&gcc GCC_UNIPHY0_PORT4_ARES>, + <&gcc GCC_UNIPHY0_PORT5_ARES>, + <&gcc GCC_UNIPHY0_PORT_4_5_RESET>, + <&gcc GCC_UNIPHY0_PORT_4_RESET>; + reset-names = "ppe_rst", "uniphy0_soft_rst", "uniphy0_xpcs_rst", + "uniphy1_soft_rst", "uniphy1_xpcs_rst", + "nss_port1_rst", "nss_port2_rst", "nss_port3_rst", + "nss_port4_rst", "nss_port5_rst", + "uniphy0_port1_dis", "uniphy0_port2_dis", + "uniphy0_port3_dis", "uniphy0_port4_dis", + "uniphy0_port5_dis", "uniphy0_port_4_5_rst", + "uniphy0_port_4_rst"; + }; + + ess-uniphy@7a00000 { + compatible = "qcom,ess-uniphy"; + reg = <0x7a00000 0x30000>; + uniphy_access_mode = "local bus"; + }; + + edma: edma@3ab00000 { + compatible = "qcom,edma"; + reg = <0x0 0x3ab00000 0x0 0xabe00>; + reg-names = "edma-reg-base"; + qcom,txdesc-ring-start = <23>; + qcom,txdesc-rings = <1>; + qcom,txcmpl-ring-start = <23>; + 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/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-qihoo-v6.dts b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-qihoo-v6.dts new file mode 100644 index 000000000..98dcab1c5 --- /dev/null +++ b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-qihoo-v6.dts @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * IPQ6018 CP01 board device tree source + * + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +/dts-v1/; + +#include "ipq6018.dtsi" + +/ { + model = "Qihoo 360v6"; + compatible = "qihoo,v6", "qcom,ipq6018"; + + aliases { + serial0 = &blsp1_uart3; + }; + + chosen { + stdout-path = "serial0:115200n8"; + bootargs-append = " swiotlb=1"; + }; +}; + +&blsp1_uart3 { + pinctrl-0 = <&serial_3_pins>; + pinctrl-names = "default"; + status = "okay"; +}; + +&blsp1_i2c3 { + pinctrl-0 = <&i2c_1_pins>; + pinctrl-names = "default"; + status = "okay"; +}; + +&blsp1_spi1 { + cs-select = <0>; + status = "okay"; + + flash@0 { + #address-cells = <1>; + #size-cells = <1>; + reg = <0>; + compatible = "micron,n25q128a11", "jedec,spi-nor"; + spi-max-frequency = <50000000>; + }; +}; + +&tlmm { + i2c_1_pins: i2c-1-pins { + pins = "gpio42", "gpio43"; + function = "blsp2_i2c"; + drive-strength = <8>; + }; + + spi_0_pins: spi-0-pins { + pins = "gpio38", "gpio39", "gpio40", "gpio41"; + function = "blsp0_spi"; + drive-strength = <8>; + bias-pull-down; + }; +}; + +&qpic_bam { + status = "okay"; +}; + +&qpic_nand { + status = "okay"; + + nand@0 { + reg = <0>; + + nand-ecc-strength = <4>; + nand-ecc-step-size = <512>; + nand-bus-width = <8>; + }; +}; + +&qusb_phy_1 { + status = "ok"; +}; + +&usb2 { + status = "ok"; +}; diff --git a/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-upstreamable.dtsi b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-upstreamable.dtsi new file mode 100644 index 000000000..dd03e63d3 --- /dev/null +++ b/target/linux/ipq60xx/files/arch/arm64/boot/dts/qcom/ipq6018-upstreamable.dtsi @@ -0,0 +1,120 @@ +/ { + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + nss@40000000 { + reg = <0x00 0x40000000 0x00 0x1000000>; + no-map; + }; + + uboot@4a100000 { + reg = <0x00 0x4a100000 0x00 0x400000>; + no-map; + }; + + sbl@4a500000 { + reg = <0x00 0x4a500000 0x00 0x100000>; + no-map; + }; + }; +}; + +&soc { + wifi: wifi@c000000 { + compatible = "qcom,ipq6018-wifi"; + reg = <0x0 0xc000000 0x0 0x1000000>; + qcom,rproc = <&q6v5_wcss>; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "misc-pulse1", "misc-latch", "sw-exception", + "watchdog", "ce0", "ce1", "ce2", "ce3", "ce4", + "ce5", "ce6", "ce7", "ce8", "ce9", "ce10", + "ce11", "host2wbm-desc-feed", + "host2reo-re-injection", "host2reo-command", + "host2rxdma-monitor-ring3", + "host2rxdma-monitor-ring2", + "host2rxdma-monitor-ring1", + "reo2ost-exception", "wbm2host-rx-release", + "reo2host-status", + "reo2host-destination-ring4", + "reo2host-destination-ring3", + "reo2host-destination-ring2", + "reo2host-destination-ring1", + "rxdma2host-monitor-destination-mac3", + "rxdma2host-monitor-destination-mac2", + "rxdma2host-monitor-destination-mac1", + "ppdu-end-interrupts-mac3", + "ppdu-end-interrupts-mac2", + "ppdu-end-interrupts-mac1", + "rxdma2host-monitor-status-ring-mac3", + "rxdma2host-monitor-status-ring-mac2", + "rxdma2host-monitor-status-ring-mac1", + "host2rxdma-host-buf-ring-mac3", + "host2rxdma-host-buf-ring-mac2", + "host2rxdma-host-buf-ring-mac1", + "rxdma2host-destination-ring-mac3", + "rxdma2host-destination-ring-mac2", + "rxdma2host-destination-ring-mac1", + "host2tcl-input-ring4", + "host2tcl-input-ring3", + "host2tcl-input-ring2", + "host2tcl-input-ring1", + "wbm2host-tx-completions-ring3", + "wbm2host-tx-completions-ring2", + "wbm2host-tx-completions-ring1", + "tcl2host-status-ring"; + status = "disabled"; + }; +}; diff --git a/target/linux/ipq60xx/generic/target.mk b/target/linux/ipq60xx/generic/target.mk new file mode 100644 index 000000000..f5cb1fb19 --- /dev/null +++ b/target/linux/ipq60xx/generic/target.mk @@ -0,0 +1 @@ +BOARDNAME:=Generic diff --git a/target/linux/ipq60xx/image/Makefile b/target/linux/ipq60xx/image/Makefile index d9d6883ce..4b0a657c0 100644 --- a/target/linux/ipq60xx/image/Makefile +++ b/target/linux/ipq60xx/image/Makefile @@ -13,23 +13,6 @@ define Device/Default IMAGE/sysupgrade.bin/squashfs := endef -define Device/FitImage - KERNEL_SUFFIX := -fit-uImage.itb - KERNEL = kernel-bin | gzip | fit gzip $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb - KERNEL_NAME := Image -endef - -define Device/FitImageLzma - KERNEL_SUFFIX := -fit-uImage.itb - KERNEL = kernel-bin | lzma | fit lzma $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb - KERNEL_NAME := Image -endef - -define Device/UbiFit - KERNEL_IN_UBI := 1 - IMAGES := nand-factory.ubi nand-sysupgrade.bin - IMAGE/nand-factory.ubi := append-ubi - IMAGE/nand-sysupgrade.bin := sysupgrade-tar | append-metadata -endef +include $(SUBTARGET).mk $(eval $(call BuildImage)) diff --git a/target/linux/ipq60xx/image/generic.mk b/target/linux/ipq60xx/image/generic.mk new file mode 100644 index 000000000..8c9dc368b --- /dev/null +++ b/target/linux/ipq60xx/image/generic.mk @@ -0,0 +1,40 @@ +define Device/FitImage + KERNEL_SUFFIX := -fit-uImage.itb + KERNEL = kernel-bin | gzip | fit gzip $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb + KERNEL_NAME := Image +endef + +define Device/FitImageLzma + KERNEL_SUFFIX := -fit-uImage.itb + KERNEL = kernel-bin | lzma | fit lzma $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb + KERNEL_NAME := Image +endef + +define Device/FitzImage + KERNEL_SUFFIX := -fit-zImage.itb + KERNEL = kernel-bin | fit none $$(DEVICE_DTS_DIR)/$$(DEVICE_DTS).dtb + KERNEL_NAME := zImage +endef + +define Device/UbiFit + KERNEL_IN_UBI := 1 + IMAGES := nand-factory.ubi nand-sysupgrade.bin + IMAGE/nand-factory.ubi := append-ubi + IMAGE/nand-sysupgrade.bin := sysupgrade-tar | append-metadata +endef + +define Device/qihoo_v6 + $(call Device/FitImage) + DEVICE_VENDOR := Qihoo + DEVICE_MODEL := 360v6 + SOC := ipq6018 +endef +TARGET_DEVICES += qihoo_v6 + +define Device/eap610-outdoor + $(call Device/FitImage) + DEVICE_VENDOR := TP-Link + DEVICE_MODEL := EAP610-Outdoor + SOC := ipq6018 +endef +TARGET_DEVICES += eap610-outdoor diff --git a/target/linux/ipq60xx/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch b/target/linux/ipq60xx/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch new file mode 100644 index 000000000..1da0d2916 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0001-v5.16-arm64-dts-qcom-ipq8074-add-SPMI-bus.patch @@ -0,0 +1,48 @@ +From 3f3f712b16c7d374cfb079ca83684f12fda7884c Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 5 Sep 2021 18:58:16 +0200 +Subject: [PATCH 01/44] 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(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 6b9ac0550490..c37de90616fa 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -293,6 +293,25 @@ gcc: gcc@1800000 { + #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>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0002-v5.16-arm64-dts-qcom-Update-BAM-DMA-node-name-per-DT-schem.patch b/target/linux/ipq60xx/patches-5.15/0002-v5.16-arm64-dts-qcom-Update-BAM-DMA-node-name-per-DT-schem.patch new file mode 100644 index 000000000..8b48317d3 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0002-v5.16-arm64-dts-qcom-Update-BAM-DMA-node-name-per-DT-schem.patch @@ -0,0 +1,32 @@ +From e5698ba1e94af28e5f54943bcd6de278efc84500 Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Tue, 31 Aug 2021 13:23:25 +0800 +Subject: [PATCH 02/44] arm64: dts: qcom: Update BAM DMA node name per DT + schema + +Follow dma-controller.yaml schema to use `dma-controller` as node name +of BAM DMA devices. + +Signed-off-by: Shawn Guo +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20210831052325.21229-1-shawn.guo@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index c37de90616fa..5acbacecbf4f 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -212,7 +212,7 @@ prng: rng@e3000 { + status = "disabled"; + }; + +- cryptobam: dma@704000 { ++ cryptobam: dma-controller@704000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x00704000 0x20000>; + interrupts = ; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0003-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch b/target/linux/ipq60xx/patches-5.15/0003-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch new file mode 100644 index 000000000..27e3824d6 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0003-v5.16-arm64-dts-qcom-ipq8074-Add-QUP5-I2C-node.patch @@ -0,0 +1,45 @@ +From 187368d2936edad6342151ae1ac34d95dc2de2c1 Mon Sep 17 00:00:00 2001 +From: Chukun Pan +Date: Fri, 1 Oct 2021 22:54:21 +0800 +Subject: [PATCH 03/44] 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(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 5acbacecbf4f..9e700963a1e3 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -430,6 +430,21 @@ blsp1_i2c3: i2c@78b7000 { + 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>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0004-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch b/target/linux/ipq60xx/patches-5.15/0004-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch new file mode 100644 index 000000000..b3aa15bb4 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0004-v5.16-arm64-dts-qcom-msm8996-Move-clock-cells-to-QMP-PHY-c.patch @@ -0,0 +1,58 @@ +From 4ef751128de689e12e3eccb5d4e2562ef8b42758 Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Wed, 29 Sep 2021 11:42:46 +0800 +Subject: [PATCH 04/44] 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 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 9e700963a1e3..69ce2dd644ab 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -91,7 +91,6 @@ soc: soc { + 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 @@ usb1_ssphy: lane@58200 { + <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 @@ qusb_phy_1: phy@59000 { + 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 @@ usb0_ssphy: lane@78200 { + <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"; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0005-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch b/target/linux/ipq60xx/patches-5.15/0005-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch new file mode 100644 index 000000000..1cc27a763 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0005-v5.16-arm64-dts-qcom-Correct-QMP-PHY-child-node-name.patch @@ -0,0 +1,50 @@ +From 616ad8e9f89e9f57597cf856697fe1c35cf84e5d Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Wed, 29 Sep 2021 11:42:47 +0800 +Subject: [PATCH 05/44] 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/ipq8074.dtsi | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 69ce2dd644ab..56c1b4cfc6ba 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -105,7 +105,7 @@ ssphy_1: phy@58000 { + 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 @@ ssphy_0: phy@78000 { + reset-names = "phy","common"; + status = "disabled"; + +- usb0_ssphy: lane@78200 { ++ usb0_ssphy: phy@78200 { + reg = <0x00078200 0x130>, /* Tx */ + <0x00078400 0x200>, /* Rx */ + <0x00078800 0x1f8>, /* PCS */ +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0006-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch b/target/linux/ipq60xx/patches-5.15/0006-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch new file mode 100644 index 000000000..bbad2303a --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0006-v5.16-arm64-dts-qcom-Fix-IPQ8074-PCIe-PHY-nodes.patch @@ -0,0 +1,99 @@ +From 2a38228b9ac3f8cb4fdae411abbdd5226b687880 Mon Sep 17 00:00:00 2001 +From: Shawn Guo +Date: Wed, 29 Sep 2021 11:42:51 +0800 +Subject: [PATCH 06/44] 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(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 56c1b4cfc6ba..4512fcac0f9a 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -174,34 +174,60 @@ qusb_phy_0: phy@79000 { + 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 { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0007-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch b/target/linux/ipq60xx/patches-5.15/0007-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch new file mode 100644 index 000000000..00f9640b2 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0007-v5.17-arm64-dts-qcom-ipq8074-add-MDIO-bus.patch @@ -0,0 +1,41 @@ +From 9757a0d4e05b807074f6868ed594a9bf0111d74d Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 7 Oct 2021 13:58:46 +0200 +Subject: [PATCH 07/44] 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(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 4512fcac0f9a..6ee54f89f973 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -230,6 +230,18 @@ pcie_phy1: phy@8e200 { + }; + }; + ++ 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>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch b/target/linux/ipq60xx/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch new file mode 100644 index 000000000..b71b66fde --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0008-v5.16-soc-qcom-smem-Support-reserved-memory-description.patch @@ -0,0 +1,172 @@ +From 00abf58f0ec5ce9dd947792f65e9d01284a4e9c8 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Thu, 30 Sep 2021 11:21:10 -0700 +Subject: [PATCH 08/44] 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(-) + +diff --git a/drivers/of/platform.c b/drivers/of/platform.c +index 74afbb7a4f5e..19ba8e4d4f8d 100644 +--- a/drivers/of/platform.c ++++ b/drivers/of/platform.c +@@ -509,6 +509,7 @@ EXPORT_SYMBOL_GPL(of_platform_default_populate); + 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" }, + {} +diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c +index 4fb5aeeb0843..c7e519bfdc8a 100644 +--- 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[] = { 0x53, 0x49, 0x49, 0x49 }; /* SIII */ + * @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 qcom_smem *smem, + 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 *p) + 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 qcom_smem *smem, u16 local_host) + 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 qcom_smem *smem, struct device *dev, + 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 qcom_smem *smem, struct device *dev, + 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 platform_device *pdev) + smem->dev = &pdev->dev; + smem->num_regions = num_regions; + +- ret = qcom_smem_map_memory(smem, &pdev->dev, "memory-region", 0); +- if (ret) +- 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_map_memory(smem, &pdev->dev, +- "qcom,rpm-msg-ram", 1))) +- 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 || +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0009-v5.18-arm64-dts-qcom-ipq8074-add-SMEM-support.patch b/target/linux/ipq60xx/patches-5.15/0009-v5.18-arm64-dts-qcom-ipq8074-add-SMEM-support.patch new file mode 100644 index 000000000..3da3aa046 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0009-v5.18-arm64-dts-qcom-ipq8074-add-SMEM-support.patch @@ -0,0 +1,56 @@ +From 0064ce4f52ed8fc010c1794114205daa9f598828 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 6 Jan 2022 22:25:12 +0100 +Subject: [PATCH 09/44] arm64: dts: qcom: 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 +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220106212512.1970828-1-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 6ee54f89f973..954f85c460ed 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -76,6 +76,20 @@ psci { + 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 @@ gcc: gcc@1800000 { + #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>, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0010-v5.18-arm64-dts-qcom-ipq8074-add-the-reserved-memory-node.patch b/target/linux/ipq60xx/patches-5.15/0010-v5.18-arm64-dts-qcom-ipq8074-add-the-reserved-memory-node.patch new file mode 100644 index 000000000..b4f3ae725 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0010-v5.18-arm64-dts-qcom-ipq8074-add-the-reserved-memory-node.patch @@ -0,0 +1,35 @@ +From d58eeedd46d47db44a5932f7d74efae881d54c9b Mon Sep 17 00:00:00 2001 +From: Kathiravan T +Date: Fri, 7 Jan 2022 18:24:38 +0530 +Subject: [PATCH 10/44] arm64: dts: qcom: ipq8074: add the reserved-memory node + +On IPQ8074, 4MB of memory is needed for TZ. So mark that region +as reserved. + +Signed-off-by: Kathiravan T +[bjorn: Squash with existing reserved-memory node] +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/1641560078-860-1-git-send-email-quic_kathirav@quicinc.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 954f85c460ed..9e8e907290a4 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -88,6 +88,11 @@ smem@4ab00000 { + + hwlocks = <&tcsr_mutex 0>; + }; ++ ++ memory@4ac00000 { ++ no-map; ++ reg = <0x0 0x4ac00000 0x0 0x00400000>; ++ }; + }; + + firmware { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0011-v5.18-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch b/target/linux/ipq60xx/patches-5.15/0011-v5.18-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch new file mode 100644 index 000000000..100738bbd --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0011-v5.18-arm64-dts-qcom-ipq8074-enable-the-GICv2m-support.patch @@ -0,0 +1,41 @@ +From 4f0959ded385c8ed518659aa08cedbd83ae0726a Mon Sep 17 00:00:00 2001 +From: Kathiravan T +Date: Tue, 8 Feb 2022 21:05:24 +0530 +Subject: [PATCH 11/44] 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 +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/1644334525-11577-2-git-send-email-quic_kathirav@quicinc.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 9e8e907290a4..c98443d4798f 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -634,9 +634,18 @@ dwc_1: dwc3@8c00000 { + + 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 { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0012-v5.18-arm64-dts-qcom-ipq8074-drop-the-clock-frequency-prop.patch b/target/linux/ipq60xx/patches-5.15/0012-v5.18-arm64-dts-qcom-ipq8074-drop-the-clock-frequency-prop.patch new file mode 100644 index 000000000..e4d31333a --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0012-v5.18-arm64-dts-qcom-ipq8074-drop-the-clock-frequency-prop.patch @@ -0,0 +1,31 @@ +From 2e21d1f48dc0d0cdbd53ac33b9859c7cb575eecc Mon Sep 17 00:00:00 2001 +From: Kathiravan T +Date: Wed, 2 Feb 2022 22:05:08 +0530 +Subject: [PATCH 12/44] arm64: dts: qcom: ipq8074: drop the clock-frequency + property + +Drop the clock-frequency property from the MMIO timer node, since it +is already configured by the bootloader. + +Signed-off-by: Kathiravan T +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/1643819709-5410-2-git-send-email-quic_kathirav@quicinc.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index c98443d4798f..664fba3632b1 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -670,7 +670,6 @@ timer@b120000 { + ranges; + compatible = "arm,armv7-timer-mem"; + reg = <0x0b120000 0x1000>; +- clock-frequency = <19200000>; + + frame@b120000 { + frame-number = <0>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0013-v5.19-arm64-dts-qcom-align-dmas-in-I2C-SPI-UART-with-DT-sc.patch b/target/linux/ipq60xx/patches-5.15/0013-v5.19-arm64-dts-qcom-align-dmas-in-I2C-SPI-UART-with-DT-sc.patch new file mode 100644 index 000000000..08160dc42 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0013-v5.19-arm64-dts-qcom-align-dmas-in-I2C-SPI-UART-with-DT-sc.patch @@ -0,0 +1,67 @@ +From 4647475f588c85138ddf47a17305dd41834e1105 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Tue, 5 Apr 2022 08:34:43 +0200 +Subject: [PATCH 13/44] arm64: dts: qcom: align dmas in I2C/SPI/UART with DT + schema + +The DT schema expects dma channels in tx-rx order. No functional +change. + +Signed-off-by: Krzysztof Kozlowski +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220405063451.12011-2-krzysztof.kozlowski@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 664fba3632b1..61fc121dce7b 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -471,8 +471,8 @@ blsp1_i2c2: i2c@78b6000 { + <&gcc GCC_BLSP1_QUP2_I2C_APPS_CLK>; + clock-names = "iface", "core"; + clock-frequency = <400000>; +- dmas = <&blsp_dma 15>, <&blsp_dma 14>; +- dma-names = "rx", "tx"; ++ dmas = <&blsp_dma 14>, <&blsp_dma 15>; ++ dma-names = "tx", "rx"; + pinctrl-0 = <&i2c_0_pins>; + pinctrl-names = "default"; + status = "disabled"; +@@ -488,8 +488,8 @@ blsp1_i2c3: i2c@78b7000 { + <&gcc GCC_BLSP1_QUP3_I2C_APPS_CLK>; + clock-names = "iface", "core"; + clock-frequency = <100000>; +- dmas = <&blsp_dma 17>, <&blsp_dma 16>; +- dma-names = "rx", "tx"; ++ dmas = <&blsp_dma 16>, <&blsp_dma 17>; ++ dma-names = "tx", "rx"; + status = "disabled"; + }; + +@@ -503,8 +503,8 @@ blsp1_i2c5: i2c@78b9000 { + <&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"; ++ dmas = <&blsp_dma 20>, <&blsp_dma 21>; ++ dma-names = "tx", "rx"; + status = "disabled"; + }; + +@@ -518,8 +518,8 @@ blsp1_i2c6: i2c@78ba000 { + <&gcc GCC_BLSP1_QUP6_I2C_APPS_CLK>; + clock-names = "iface", "core"; + clock-frequency = <100000>; +- dmas = <&blsp_dma 23>, <&blsp_dma 22>; +- dma-names = "rx", "tx"; ++ dmas = <&blsp_dma 22>, <&blsp_dma 23>; ++ dma-names = "tx", "rx"; + status = "disabled"; + }; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0014-v5.19-arm64-dts-qcom-align-clocks-in-I2C-SPI-with-DT-schem.patch b/target/linux/ipq60xx/patches-5.15/0014-v5.19-arm64-dts-qcom-align-clocks-in-I2C-SPI-with-DT-schem.patch new file mode 100644 index 000000000..ad9e1e4ac --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0014-v5.19-arm64-dts-qcom-align-clocks-in-I2C-SPI-with-DT-schem.patch @@ -0,0 +1,74 @@ +From 553f1ea4128453cead2d38d5773ec6044c6e7626 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Tue, 5 Apr 2022 08:34:44 +0200 +Subject: [PATCH 14/44] arm64: dts: qcom: align clocks in I2C/SPI with DT + schema + +The DT schema expects clocks core-iface order. No functional change. + +Signed-off-by: Krzysztof Kozlowski +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220405063451.12011-3-krzysztof.kozlowski@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 61fc121dce7b..e771ceea7453 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -467,9 +467,9 @@ blsp1_i2c2: i2c@78b6000 { + #size-cells = <0>; + reg = <0x078b6000 0x600>; + interrupts = ; +- clocks = <&gcc GCC_BLSP1_AHB_CLK>, +- <&gcc GCC_BLSP1_QUP2_I2C_APPS_CLK>; +- clock-names = "iface", "core"; ++ clocks = <&gcc GCC_BLSP1_QUP2_I2C_APPS_CLK>, ++ <&gcc GCC_BLSP1_AHB_CLK>; ++ clock-names = "core", "iface"; + clock-frequency = <400000>; + dmas = <&blsp_dma 14>, <&blsp_dma 15>; + dma-names = "tx", "rx"; +@@ -484,9 +484,9 @@ blsp1_i2c3: i2c@78b7000 { + #size-cells = <0>; + reg = <0x078b7000 0x600>; + interrupts = ; +- clocks = <&gcc GCC_BLSP1_AHB_CLK>, +- <&gcc GCC_BLSP1_QUP3_I2C_APPS_CLK>; +- clock-names = "iface", "core"; ++ clocks = <&gcc GCC_BLSP1_QUP3_I2C_APPS_CLK>, ++ <&gcc GCC_BLSP1_AHB_CLK>; ++ clock-names = "core", "iface"; + clock-frequency = <100000>; + dmas = <&blsp_dma 16>, <&blsp_dma 17>; + dma-names = "tx", "rx"; +@@ -499,9 +499,9 @@ blsp1_i2c5: i2c@78b9000 { + #size-cells = <0>; + reg = <0x78b9000 0x600>; + interrupts = ; +- clocks = <&gcc GCC_BLSP1_AHB_CLK>, +- <&gcc GCC_BLSP1_QUP5_I2C_APPS_CLK>; +- clock-names = "iface", "core"; ++ clocks = <&gcc GCC_BLSP1_QUP5_I2C_APPS_CLK>, ++ <&gcc GCC_BLSP1_AHB_CLK>; ++ clock-names = "core", "iface"; + clock-frequency = <400000>; + dmas = <&blsp_dma 20>, <&blsp_dma 21>; + dma-names = "tx", "rx"; +@@ -514,9 +514,9 @@ blsp1_i2c6: i2c@78ba000 { + #size-cells = <0>; + reg = <0x078ba000 0x600>; + interrupts = ; +- clocks = <&gcc GCC_BLSP1_AHB_CLK>, +- <&gcc GCC_BLSP1_QUP6_I2C_APPS_CLK>; +- clock-names = "iface", "core"; ++ clocks = <&gcc GCC_BLSP1_QUP6_I2C_APPS_CLK>, ++ <&gcc GCC_BLSP1_AHB_CLK>; ++ clock-names = "core", "iface"; + clock-frequency = <100000>; + dmas = <&blsp_dma 22>, <&blsp_dma 23>; + dma-names = "tx", "rx"; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0015-v5.19-arm64-dts-qcom-correct-DWC3-node-names-and-unit-addr.patch b/target/linux/ipq60xx/patches-5.15/0015-v5.19-arm64-dts-qcom-correct-DWC3-node-names-and-unit-addr.patch new file mode 100644 index 000000000..b08202256 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0015-v5.19-arm64-dts-qcom-correct-DWC3-node-names-and-unit-addr.patch @@ -0,0 +1,42 @@ +From 04e91eb95f7a0a9dceed2c1fcb47fbbe7f96ec52 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Wed, 4 May 2022 15:19:16 +0200 +Subject: [PATCH 15/44] arm64: dts: qcom: correct DWC3 node names and unit + addresses + +Align DWC3 USB node names with DT schema ("usb" is expected) and correct +the unit addresses to match the "reg" property. This also implies +overriding nodes by label, instead of full path. + +Signed-off-by: Krzysztof Kozlowski +Link: https://lore.kernel.org/r/20220504131923.214367-7-krzysztof.kozlowski@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index e771ceea7453..51c3eb1bd393 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -578,7 +578,7 @@ usb_0: usb@8af8800 { + resets = <&gcc GCC_USB0_BCR>; + status = "disabled"; + +- dwc_0: dwc3@8a00000 { ++ dwc_0: usb@8a00000 { + compatible = "snps,dwc3"; + reg = <0x8a00000 0xcd00>; + interrupts = ; +@@ -618,7 +618,7 @@ usb_1: usb@8cf8800 { + resets = <&gcc GCC_USB1_BCR>; + status = "disabled"; + +- dwc_1: dwc3@8c00000 { ++ dwc_1: usb@8c00000 { + compatible = "snps,dwc3"; + reg = <0x8c00000 0xcd00>; + interrupts = ; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0016-v5.19-arm64-dts-qcom-ipq8074-add-dedicated-qcom-ipq8074-dw.patch b/target/linux/ipq60xx/patches-5.15/0016-v5.19-arm64-dts-qcom-ipq8074-add-dedicated-qcom-ipq8074-dw.patch new file mode 100644 index 000000000..08050ee65 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0016-v5.19-arm64-dts-qcom-ipq8074-add-dedicated-qcom-ipq8074-dw.patch @@ -0,0 +1,41 @@ +From 65b572a6c5bad2c9f3c784ff42d4eddedcfd85cd Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Wed, 4 May 2022 15:19:17 +0200 +Subject: [PATCH 16/44] arm64: dts: qcom: ipq8074: add dedicated + qcom,ipq8074-dwc3 compatible + +Add dedicated compatible for DWC3 USB node name to allow more accurate +DT schema matching. + +Signed-off-by: Krzysztof Kozlowski +Link: https://lore.kernel.org/r/20220504131923.214367-8-krzysztof.kozlowski@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 51c3eb1bd393..4d3ecb09de9c 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -553,7 +553,7 @@ qpic_nand: nand-controller@79b0000 { + }; + + usb_0: usb@8af8800 { +- compatible = "qcom,dwc3"; ++ compatible = "qcom,ipq8074-dwc3", "qcom,dwc3"; + reg = <0x08af8800 0x400>; + #address-cells = <1>; + #size-cells = <1>; +@@ -593,7 +593,7 @@ dwc_0: usb@8a00000 { + }; + + usb_1: usb@8cf8800 { +- compatible = "qcom,dwc3"; ++ compatible = "qcom,ipq8074-dwc3", "qcom,dwc3"; + reg = <0x08cf8800 0x400>; + #address-cells = <1>; + #size-cells = <1>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0017-v5.19-arm64-dts-qcom-align-DWC3-USB-clocks-with-DT-schema.patch b/target/linux/ipq60xx/patches-5.15/0017-v5.19-arm64-dts-qcom-align-DWC3-USB-clocks-with-DT-schema.patch new file mode 100644 index 000000000..3c494cd74 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0017-v5.19-arm64-dts-qcom-align-DWC3-USB-clocks-with-DT-schema.patch @@ -0,0 +1,44 @@ +From e8949160470080b4c24139cfb88accc25e589f70 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Wed, 4 May 2022 15:19:22 +0200 +Subject: [PATCH 17/44] arm64: dts: qcom: align DWC3 USB clocks with DT schema + +Align order of clocks and their names with Qualcomm DWC3 USB DT schema. +No functional impact expected. + +Signed-off-by: Krzysztof Kozlowski +Link: https://lore.kernel.org/r/20220504131923.214367-13-krzysztof.kozlowski@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 4d3ecb09de9c..697f46e17903 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -563,8 +563,8 @@ usb_0: usb@8af8800 { + <&gcc GCC_USB0_MASTER_CLK>, + <&gcc GCC_USB0_SLEEP_CLK>, + <&gcc GCC_USB0_MOCK_UTMI_CLK>; +- clock-names = "sys_noc_axi", +- "master", ++ clock-names = "cfg_noc", ++ "core", + "sleep", + "mock_utmi"; + +@@ -603,8 +603,8 @@ usb_1: usb@8cf8800 { + <&gcc GCC_USB1_MASTER_CLK>, + <&gcc GCC_USB1_SLEEP_CLK>, + <&gcc GCC_USB1_MOCK_UTMI_CLK>; +- clock-names = "sys_noc_axi", +- "master", ++ clock-names = "cfg_noc", ++ "core", + "sleep", + "mock_utmi"; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0018-v6.0-arm64-dts-qcom-adjust-whitespace-around.patch b/target/linux/ipq60xx/patches-5.15/0018-v6.0-arm64-dts-qcom-adjust-whitespace-around.patch new file mode 100644 index 000000000..2e1f69770 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0018-v6.0-arm64-dts-qcom-adjust-whitespace-around.patch @@ -0,0 +1,41 @@ +From af38dc1085f574d575c886c0800645c8b7d4b874 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Thu, 26 May 2022 22:42:47 +0200 +Subject: [PATCH 18/44] arm64: dts: qcom: adjust whitespace around '=' + +Fix whitespace coding style: use single space instead of tabs or +multiple spaces around '=' sign in property assignment. No functional +changes (same DTB). + +Signed-off-by: Krzysztof Kozlowski +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220526204248.832139-1-krzysztof.kozlowski@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 697f46e17903..6a4bcf3ef94e 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -119,7 +119,7 @@ ssphy_1: phy@58000 { + <&xo>; + clock-names = "aux", "cfg_ahb", "ref"; + +- resets = <&gcc GCC_USB1_PHY_BCR>, ++ resets = <&gcc GCC_USB1_PHY_BCR>, + <&gcc GCC_USB3PHY_1_PHY_BCR>; + reset-names = "phy","common"; + status = "disabled"; +@@ -162,7 +162,7 @@ ssphy_0: phy@78000 { + <&xo>; + clock-names = "aux", "cfg_ahb", "ref"; + +- resets = <&gcc GCC_USB0_PHY_BCR>, ++ resets = <&gcc GCC_USB0_PHY_BCR>, + <&gcc GCC_USB3PHY_0_PHY_BCR>; + reset-names = "phy","common"; + status = "disabled"; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0019-v6.0-arm64-dts-qcom-Fix-sdhci-node-names-use-mmc.patch b/target/linux/ipq60xx/patches-5.15/0019-v6.0-arm64-dts-qcom-Fix-sdhci-node-names-use-mmc.patch new file mode 100644 index 000000000..f1a55ea5d --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0019-v6.0-arm64-dts-qcom-Fix-sdhci-node-names-use-mmc.patch @@ -0,0 +1,39 @@ +From 0d7e4bd5d554ac7471724f80aa67b664f3539f47 Mon Sep 17 00:00:00 2001 +From: Bhupesh Sharma +Date: Sun, 15 May 2022 03:24:19 +0530 +Subject: [PATCH 19/44] arm64: dts: qcom: Fix sdhci node names - use 'mmc@' + +Since the Qualcomm sdhci-msm device-tree binding has been converted +to yaml format, 'make dtbs_check' reports issues with +inconsistent 'sdhci@' convention used for specifying the +sdhci nodes. The generic mmc bindings expect 'mmc@' format +instead. + +Fix the same. + +Cc: Bjorn Andersson +Cc: Rob Herring +Signed-off-by: Bhupesh Sharma +[bjorn: Moved non-arm64 changes to separate commit] +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220514215424.1007718-2-bhupesh.sharma@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 6a4bcf3ef94e..3658f41393e1 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -375,7 +375,7 @@ spmi_bus: spmi@200f000 { + cell-index = <0>; + }; + +- sdhc_1: sdhci@7824900 { ++ sdhc_1: mmc@7824900 { + compatible = "qcom,sdhci-msm-v4"; + reg = <0x7824900 0x500>, <0x7824000 0x800>; + reg-names = "hc_mem", "core_mem"; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0020-v6.0-arm64-dts-qcom-Fix-ordering-of-clocks-clock-names-fo.patch b/target/linux/ipq60xx/patches-5.15/0020-v6.0-arm64-dts-qcom-Fix-ordering-of-clocks-clock-names-fo.patch new file mode 100644 index 000000000..5758242b6 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0020-v6.0-arm64-dts-qcom-Fix-ordering-of-clocks-clock-names-fo.patch @@ -0,0 +1,52 @@ +From d923ec8397673773a3d22dc3ac0c5fccd22cd405 Mon Sep 17 00:00:00 2001 +From: Bhupesh Sharma +Date: Sun, 15 May 2022 03:24:22 +0530 +Subject: [PATCH 20/44] arm64: dts: qcom: Fix ordering of 'clocks' & + 'clock-names' for sdhci nodes + +Since the Qualcomm sdhci-msm device-tree binding has been converted +to yaml format, 'make dtbs_check' reports a number of issues with +ordering of 'clocks' & 'clock-names' for sdhci nodes: + + arch/arm64/boot/dts/qcom/ipq8074-hk10-c2.dtb: sdhci@7824900: + clock-names:0: 'iface' was expected + + arch/arm64/boot/dts/qcom/ipq8074-hk10-c2.dtb: sdhci@7824900: + clock-names:1: 'core' was expected + + arch/arm64/boot/dts/qcom/ipq8074-hk10-c2.dtb: sdhci@7824900: + clock-names:2: 'xo' was expected + +Fix the same by updating the offending 'dts' files. + +Cc: Bjorn Andersson +Cc: Rob Herring +Signed-off-by: Bhupesh Sharma +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220514215424.1007718-5-bhupesh.sharma@linaro.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 3658f41393e1..cdeb6e3f9a92 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -384,10 +384,10 @@ sdhc_1: mmc@7824900 { + ; + interrupt-names = "hc_irq", "pwr_irq"; + +- clocks = <&xo>, +- <&gcc GCC_SDCC1_AHB_CLK>, +- <&gcc GCC_SDCC1_APPS_CLK>; +- clock-names = "xo", "iface", "core"; ++ clocks = <&gcc GCC_SDCC1_AHB_CLK>, ++ <&gcc GCC_SDCC1_APPS_CLK>, ++ <&xo>; ++ clock-names = "iface", "core", "xo"; + max-frequency = <384000000>; + mmc-ddr-1_8v; + mmc-hs200-1_8v; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0021-v6.0-dt-bindings-clock-qcom-ipq8074-add-PPE-crypto-clock.patch b/target/linux/ipq60xx/patches-5.15/0021-v6.0-dt-bindings-clock-qcom-ipq8074-add-PPE-crypto-clock.patch new file mode 100644 index 000000000..3517a9655 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0021-v6.0-dt-bindings-clock-qcom-ipq8074-add-PPE-crypto-clock.patch @@ -0,0 +1,30 @@ +From f56aefe67c7652d38293afa83333c9228a9fbc35 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 15 May 2022 23:00:41 +0200 +Subject: [PATCH 21/44] dt-bindings: clock: qcom: ipq8074: add PPE crypto clock + +Add binding for the PPE crypto clock in IPQ8074. + +Signed-off-by: Robert Marko +Acked-by: Krzysztof Kozlowski +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220515210048.483898-4-robimarko@gmail.com +--- + include/dt-bindings/clock/qcom,gcc-ipq8074.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/include/dt-bindings/clock/qcom,gcc-ipq8074.h b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +index 8e2bec1c91bf..5f0928785d7a 100644 +--- 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 +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0022-v6.0-clk-qcom-ipq8074-add-PPE-crypto-clock.patch b/target/linux/ipq60xx/patches-5.15/0022-v6.0-clk-qcom-ipq8074-add-PPE-crypto-clock.patch new file mode 100644 index 000000000..a3e8c1ca9 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0022-v6.0-clk-qcom-ipq8074-add-PPE-crypto-clock.patch @@ -0,0 +1,57 @@ +From 3c47c458fdf4056d4682cf2474e5599d5a916b61 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 15 May 2022 23:00:42 +0200 +Subject: [PATCH 22/44] 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 +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220515210048.483898-5-robimarko@gmail.com +--- + drivers/clk/qcom/gcc-ipq8074.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/drivers/clk/qcom/gcc-ipq8074.c b/drivers/clk/qcom/gcc-ipq8074.c +index d6d5defb82c9..bbd8c8f0ed6f 100644 +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -3183,6 +3183,24 @@ static struct clk_branch gcc_nss_ptp_ref_clk = { + }, + }; + ++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 = { +@@ -4655,6 +4673,7 @@ static struct clk_regmap *gcc_ipq8074_clks[] = { + [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[] = { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0023-v6.0-dt-bindings-clock-qcom-ipq8074-add-USB-GDSCs.patch b/target/linux/ipq60xx/patches-5.15/0023-v6.0-dt-bindings-clock-qcom-ipq8074-add-USB-GDSCs.patch new file mode 100644 index 000000000..5124726a2 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0023-v6.0-dt-bindings-clock-qcom-ipq8074-add-USB-GDSCs.patch @@ -0,0 +1,30 @@ +From c0823201046e5a79823e3ff5fa4f0a9b5fcda14d Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 15 May 2022 23:00:45 +0200 +Subject: [PATCH 23/44] dt-bindings: clock: qcom: ipq8074: add USB GDSCs + +Add bindings for the USB GDSCs found in IPQ8074 GCC. + +Signed-off-by: Robert Marko +Acked-by: Krzysztof Kozlowski +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220515210048.483898-8-robimarko@gmail.com +--- + include/dt-bindings/clock/qcom,gcc-ipq8074.h | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/include/dt-bindings/clock/qcom,gcc-ipq8074.h b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +index 5f0928785d7a..e4991d303708 100644 +--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +@@ -368,4 +368,7 @@ + #define GCC_PCIE1_AXI_MASTER_STICKY_ARES 130 + #define GCC_PCIE0_AXI_SLAVE_STICKY_ARES 131 + ++#define USB0_GDSC 0 ++#define USB1_GDSC 1 ++ + #endif +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0024-v6.0-clk-qcom-ipq8074-add-USB-GDSCs.patch b/target/linux/ipq60xx/patches-5.15/0024-v6.0-clk-qcom-ipq8074-add-USB-GDSCs.patch new file mode 100644 index 000000000..4ea5e06e3 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0024-v6.0-clk-qcom-ipq8074-add-USB-GDSCs.patch @@ -0,0 +1,86 @@ +From 248fb3267c23749059baa231e21650a68771abef Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 15 May 2022 23:00:46 +0200 +Subject: [PATCH 24/44] clk: qcom: ipq8074: add USB GDSCs + +Add GDSC-s for each of the two USB controllers built-in the IPQ8074. + +Signed-off-by: Robert Marko +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220515210048.483898-9-robimarko@gmail.com +--- + drivers/clk/qcom/Kconfig | 1 + + drivers/clk/qcom/gcc-ipq8074.c | 24 ++++++++++++++++++++++++ + 2 files changed, 25 insertions(+) + +diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig +index 9ef007b3cf9b..3c84e34353a4 100644 +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -166,6 +166,7 @@ config IPQ_LCC_806X + + config IPQ_GCC_8074 + tristate "IPQ8074 Global Clock Controller" ++ select QCOM_GDSC + help + Support for global clock controller on ipq8074 devices. + Say Y if you want to use peripheral devices such as UART, SPI, +diff --git a/drivers/clk/qcom/gcc-ipq8074.c b/drivers/clk/qcom/gcc-ipq8074.c +index bbd8c8f0ed6f..42d185fe19c8 100644 +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -22,6 +22,7 @@ + #include "clk-alpha-pll.h" + #include "clk-regmap-divider.h" + #include "clk-regmap-mux.h" ++#include "gdsc.h" + #include "reset.h" + + enum { +@@ -4408,6 +4409,22 @@ static struct clk_branch gcc_pcie0_axi_s_bridge_clk = { + }, + }; + ++static struct gdsc usb0_gdsc = { ++ .gdscr = 0x3e078, ++ .pd = { ++ .name = "usb0_gdsc", ++ }, ++ .pwrsts = PWRSTS_OFF_ON, ++}; ++ ++static struct gdsc usb1_gdsc = { ++ .gdscr = 0x3f078, ++ .pd = { ++ .name = "usb1_gdsc", ++ }, ++ .pwrsts = PWRSTS_OFF_ON, ++}; ++ + static const struct alpha_pll_config ubi32_pll_config = { + .l = 0x4e, + .config_ctl_val = 0x200d4aa8, +@@ -4811,6 +4828,11 @@ static const struct qcom_reset_map gcc_ipq8074_resets[] = { + [GCC_PCIE1_AXI_MASTER_STICKY_ARES] = { 0x76040, 6 }, + }; + ++static struct gdsc *gcc_ipq8074_gdscs[] = { ++ [USB0_GDSC] = &usb0_gdsc, ++ [USB1_GDSC] = &usb1_gdsc, ++}; ++ + static const struct of_device_id gcc_ipq8074_match_table[] = { + { .compatible = "qcom,gcc-ipq8074" }, + { } +@@ -4833,6 +4855,8 @@ static const struct qcom_cc_desc gcc_ipq8074_desc = { + .num_resets = ARRAY_SIZE(gcc_ipq8074_resets), + .clk_hws = gcc_ipq8074_hws, + .num_clk_hws = ARRAY_SIZE(gcc_ipq8074_hws), ++ .gdscs = gcc_ipq8074_gdscs, ++ .num_gdscs = ARRAY_SIZE(gcc_ipq8074_gdscs), + }; + + static int gcc_ipq8074_probe(struct platform_device *pdev) +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0025-v6.0-arm64-dts-qcom-ipq8074-add-USB-power-domains.patch b/target/linux/ipq60xx/patches-5.15/0025-v6.0-arm64-dts-qcom-ipq8074-add-USB-power-domains.patch new file mode 100644 index 000000000..13fad5456 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0025-v6.0-arm64-dts-qcom-ipq8074-add-USB-power-domains.patch @@ -0,0 +1,48 @@ +From e8516c04110438230314ddbe94879ecb4a5db5df Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 15 May 2022 23:00:48 +0200 +Subject: [PATCH 25/44] arm64: dts: qcom: ipq8074: add USB power domains + +Add USB power domains provided by GCC GDSCs. +Add the required #power-domain-cells to the GCC as well. + +Signed-off-by: Robert Marko +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220515210048.483898-11-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index cdeb6e3f9a92..a6cb0dafcc17 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -347,6 +347,7 @@ gcc: gcc@1800000 { + compatible = "qcom,gcc-ipq8074"; + reg = <0x01800000 0x80000>; + #clock-cells = <0x1>; ++ #power-domain-cells = <1>; + #reset-cells = <0x1>; + }; + +@@ -575,6 +576,8 @@ usb_0: usb@8af8800 { + <133330000>, + <19200000>; + ++ power-domains = <&gcc USB0_GDSC>; ++ + resets = <&gcc GCC_USB0_BCR>; + status = "disabled"; + +@@ -615,6 +618,8 @@ usb_1: usb@8cf8800 { + <133330000>, + <19200000>; + ++ power-domains = <&gcc USB1_GDSC>; ++ + resets = <&gcc GCC_USB1_BCR>; + status = "disabled"; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0026-v6.0-arm64-dts-qcom-ipq8074-move-ARMv8-timer-out-of-SoC-n.patch b/target/linux/ipq60xx/patches-5.15/0026-v6.0-arm64-dts-qcom-ipq8074-move-ARMv8-timer-out-of-SoC-n.patch new file mode 100644 index 000000000..f291cd711 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0026-v6.0-arm64-dts-qcom-ipq8074-move-ARMv8-timer-out-of-SoC-n.patch @@ -0,0 +1,56 @@ +From 510f246cf8f6af8c0d7a46d22448b812fd9a14a4 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 4 Jul 2022 13:33:18 +0200 +Subject: [PATCH 26/44] arm64: dts: qcom: ipq8074: move ARMv8 timer out of SoC + node + +The ARM timer is usually considered not part of SoC node, just like +other ARM designed blocks (PMU, PSCI). This fixes dtbs_check warning: + +arch/arm64/boot/dts/qcom/ipq8072-ax9000.dtb: soc: timer: {'compatible': ['arm,armv8-timer'], 'interrupts': [[1, 2, 3848], [1, 3, 3848], [1, 4, 3848], [1, 1, 3848]]} should not be valid under {'type': 'object'} + From schema: dtschema/schemas/simple-bus.yaml + +Signed-off-by: Robert Marko +Acked-by: Krzysztof Kozlowski +[bjorn: Moved node after "soc" for alphabetical ordering] +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220704113318.623102-1-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index a6cb0dafcc17..3779563527fa 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -653,14 +653,6 @@ v2m@0 { + }; + }; + +- timer { +- compatible = "arm,armv8-timer"; +- interrupts = , +- , +- , +- ; +- }; +- + watchdog: watchdog@b017000 { + compatible = "qcom,kpss-wdt"; + reg = <0xb017000 0x1000>; +@@ -852,4 +844,12 @@ IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + status = "disabled"; + }; + }; ++ ++ timer { ++ compatible = "arm,armv8-timer"; ++ interrupts = , ++ , ++ , ++ ; ++ }; + }; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0027-v6.0-arm64-dts-qcom-ipq8074-add-reset-to-SDHCI.patch b/target/linux/ipq60xx/patches-5.15/0027-v6.0-arm64-dts-qcom-ipq8074-add-reset-to-SDHCI.patch new file mode 100644 index 000000000..d75d9d77e --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0027-v6.0-arm64-dts-qcom-ipq8074-add-reset-to-SDHCI.patch @@ -0,0 +1,32 @@ +From 8cef5828706a1584b4dadf3f4b707a392bc8c231 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 4 Jul 2022 16:35:54 +0200 +Subject: [PATCH 27/44] arm64: dts: qcom: ipq8074: add reset to SDHCI + +Add reset to SDHCI controller so it can be reset to avoid timeout issues +after software reset due to bootloader set configuration. + +Signed-off-by: Robert Marko +Reviewed-by: Konrad Dybcio +Acked-by: Krzysztof Kozlowski +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220704143554.1180927-2-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 3779563527fa..c4f9bec9a39b 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -389,6 +389,7 @@ sdhc_1: mmc@7824900 { + <&gcc GCC_SDCC1_APPS_CLK>, + <&xo>; + clock-names = "iface", "core", "xo"; ++ resets = <&gcc GCC_SDCC1_BCR>; + max-frequency = <384000000>; + mmc-ddr-1_8v; + mmc-hs200-1_8v; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0028-v6.0-arm64-dts-qcom-ipq8074-drop-USB-PHY-clock-index.patch b/target/linux/ipq60xx/patches-5.15/0028-v6.0-arm64-dts-qcom-ipq8074-drop-USB-PHY-clock-index.patch new file mode 100644 index 000000000..a03a0f749 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0028-v6.0-arm64-dts-qcom-ipq8074-drop-USB-PHY-clock-index.patch @@ -0,0 +1,41 @@ +From 2bf80b5de7c2598972af28adc09e8e667b528d11 Mon Sep 17 00:00:00 2001 +From: Johan Hovold +Date: Tue, 5 Jul 2022 13:40:22 +0200 +Subject: [PATCH 28/44] arm64: dts: qcom: ipq8074: drop USB PHY clock index + +The QMP USB PHY provides a single clock so drop the redundant clock +index. + +Signed-off-by: Johan Hovold +Reviewed-by: Dmitry Baryshkov +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220705114032.22787-5-johan+linaro@kernel.org +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index c4f9bec9a39b..edcb6a500175 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -130,7 +130,7 @@ usb1_ssphy: phy@58200 { + <0x00058800 0x1f8>, /* PCS */ + <0x00058600 0x044>; /* PCS misc*/ + #phy-cells = <0>; +- #clock-cells = <1>; ++ #clock-cells = <0>; + clocks = <&gcc GCC_USB1_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "gcc_usb1_pipe_clk_src"; +@@ -173,7 +173,7 @@ usb0_ssphy: phy@78200 { + <0x00078800 0x1f8>, /* PCS */ + <0x00078600 0x044>; /* PCS misc*/ + #phy-cells = <0>; +- #clock-cells = <1>; ++ #clock-cells = <0>; + clocks = <&gcc GCC_USB0_PIPE_CLK>; + clock-names = "pipe0"; + clock-output-names = "gcc_usb0_pipe_clk_src"; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0029-v6.0-arm64-dts-qcom-ipq8074-add-APCS-node.patch b/target/linux/ipq60xx/patches-5.15/0029-v6.0-arm64-dts-qcom-ipq8074-add-APCS-node.patch new file mode 100644 index 000000000..843cb678b --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0029-v6.0-arm64-dts-qcom-ipq8074-add-APCS-node.patch @@ -0,0 +1,42 @@ +From 73ea147a5caa82c94486076186af2c7dc1894a97 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 7 Jul 2022 19:37:33 +0200 +Subject: [PATCH 29/44] arm64: dts: qcom: ipq8074: add APCS node + +APCS now has support for providing the APSS clocks as the child device +for IPQ8074. + +So, add the required DT node for it as it will later be used as the CPU +clocksource. + +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +[bjorn: Sorted node based on address] +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220707173733.404947-4-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index edcb6a500175..14487a39fb6c 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -662,6 +662,14 @@ watchdog: watchdog@b017000 { + timeout-sec = <30>; + }; + ++ apcs_glb: mailbox@b111000 { ++ compatible = "qcom,ipq8074-apcs-apps-global"; ++ reg = <0x0b111000 0x6000>; ++ ++ #clock-cells = <1>; ++ #mbox-cells = <1>; ++ }; ++ + timer@b120000 { + #address-cells = <1>; + #size-cells = <1>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0030-v6.0-arm64-dts-qcom-ipq8074-add-size-address-cells-to-DTS.patch b/target/linux/ipq60xx/patches-5.15/0030-v6.0-arm64-dts-qcom-ipq8074-add-size-address-cells-to-DTS.patch new file mode 100644 index 000000000..a2ab6324f --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0030-v6.0-arm64-dts-qcom-ipq8074-add-size-address-cells-to-DTS.patch @@ -0,0 +1,64 @@ +From 254bf23fe2e0c73d75a0bf4f37579e15433b75e0 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 8 Jul 2022 15:38:45 +0200 +Subject: [PATCH 30/44] arm64: dts: qcom: ipq8074: add #size/address-cells to + DTSI + +Add #size-cells and #address-cells to the SoC DTSI to avoid duplicating +the same properties in board DTS files. + +Remove the mentioned properties from current board DTS files. + +Signed-off-by: Robert Marko +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220708133846.599735-1-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074-hk01.dts | 2 -- + arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi | 3 --- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 3 +++ + 3 files changed, 3 insertions(+), 5 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts +index cc08dc4eb56a..3308e37f6abb 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts ++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts +@@ -5,8 +5,6 @@ + #include "ipq8074.dtsi" + + / { +- #address-cells = <0x2>; +- #size-cells = <0x2>; + model = "Qualcomm Technologies, Inc. IPQ8074-HK01"; + compatible = "qcom,ipq8074-hk01", "qcom,ipq8074"; + interrupt-parent = <&intc>; +diff --git a/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi b/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi +index 07e670829676..0043cc23cf7d 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi +@@ -7,9 +7,6 @@ + #include "ipq8074.dtsi" + + / { +- #address-cells = <0x2>; +- #size-cells = <0x2>; +- + interrupt-parent = <&intc>; + + aliases { +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 14487a39fb6c..e516d13b8f98 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -7,6 +7,9 @@ + #include + + / { ++ #address-cells = <2>; ++ #size-cells = <2>; ++ + model = "Qualcomm Technologies, Inc. IPQ8074"; + compatible = "qcom,ipq8074"; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0031-v6.0-arm64-dts-qcom-ipq8074-add-interrupt-parent-to-DTSI.patch b/target/linux/ipq60xx/patches-5.15/0031-v6.0-arm64-dts-qcom-ipq8074-add-interrupt-parent-to-DTSI.patch new file mode 100644 index 000000000..7e0373998 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0031-v6.0-arm64-dts-qcom-ipq8074-add-interrupt-parent-to-DTSI.patch @@ -0,0 +1,59 @@ +From 520877b4d26ba4e6d08b5e9579f166ca2a934e81 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 8 Jul 2022 15:38:46 +0200 +Subject: [PATCH 31/44] arm64: dts: qcom: ipq8074: add interrupt-parent to DTSI + +Add interrupt-parent to the SoC DTSI to avoid duplicating it in each board +DTS file. + +Remove interrupt-parent from existing board DTS files. + +Signed-off-by: Robert Marko +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/20220708133846.599735-2-robimarko@gmail.com +--- + arch/arm64/boot/dts/qcom/ipq8074-hk01.dts | 1 - + arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi | 2 -- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 1 + + 3 files changed, 1 insertion(+), 3 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts +index 3308e37f6abb..ef30053136e3 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts ++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts +@@ -7,7 +7,6 @@ + / { + model = "Qualcomm Technologies, Inc. IPQ8074-HK01"; + compatible = "qcom,ipq8074-hk01", "qcom,ipq8074"; +- interrupt-parent = <&intc>; + + aliases { + serial0 = &blsp1_uart5; +diff --git a/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi b/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi +index 0043cc23cf7d..c479a3fd0f40 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk10.dtsi +@@ -7,8 +7,6 @@ + #include "ipq8074.dtsi" + + / { +- interrupt-parent = <&intc>; +- + aliases { + serial0 = &blsp1_uart5; + }; +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index e516d13b8f98..d53675fc1595 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -12,6 +12,7 @@ / { + + model = "Qualcomm Technologies, Inc. IPQ8074"; + compatible = "qcom,ipq8074"; ++ interrupt-parent = <&intc>; + + clocks { + sleep_clk: sleep_clk { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0032-v6.0-regulator-qcom_spmi-add-support-for-HT_P150.patch b/target/linux/ipq60xx/patches-5.15/0032-v6.0-regulator-qcom_spmi-add-support-for-HT_P150.patch new file mode 100644 index 000000000..9f66f30e0 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0032-v6.0-regulator-qcom_spmi-add-support-for-HT_P150.patch @@ -0,0 +1,63 @@ +From 8928dcb644461e72608dbea26af4d072868ae41b Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 4 Jul 2022 23:23:54 +0200 +Subject: [PATCH 32/44] regulator: qcom_spmi: add support for HT_P150 + +HT_P150 is a LDO PMOS regulator based on LV P150 using HFS430 layout +found in PMP8074 and PMS405 PMIC-s. + +Both PMP8074 and PMS405 define the programmable range as 1.616V to 3.304V +but the actual MAX output voltage depends on the exact LDO in each of +the PMIC-s. + +It has a max current of 150mA, voltage step of 8mV. + +Signed-off-by: Robert Marko +Link: https://lore.kernel.org/r/20220704212402.1715182-4-robimarko@gmail.com +Signed-off-by: Mark Brown +--- + drivers/regulator/qcom_spmi-regulator.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c +index 41424a3366d0..49e5fe168391 100644 +--- 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_HT_P150 = 0x35, + }; + + enum spmi_common_regulator_registers { +@@ -544,6 +545,10 @@ static struct spmi_voltage_range hfs430_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 320000, 320000, 2040000, 2040000, 8000), + }; + ++static struct spmi_voltage_range ht_p150_ranges[] = { ++ SPMI_VOLTAGE_RANGE(0, 1616000, 1616000, 3304000, 3304000, 8000), ++}; ++ + static DEFINE_SPMI_SET_POINTS(pldo); + static DEFINE_SPMI_SET_POINTS(nldo1); + static DEFINE_SPMI_SET_POINTS(nldo2); +@@ -564,6 +569,7 @@ static DEFINE_SPMI_SET_POINTS(nldo660); + static DEFINE_SPMI_SET_POINTS(ht_lvpldo); + static DEFINE_SPMI_SET_POINTS(ht_nldo); + static DEFINE_SPMI_SET_POINTS(hfs430); ++static DEFINE_SPMI_SET_POINTS(ht_p150); + + static inline int spmi_vreg_read(struct spmi_regulator *vreg, u16 addr, u8 *buf, + int len) +@@ -1458,6 +1464,7 @@ static const struct regulator_ops spmi_hfs430_ops = { + + 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, HFS430, hfs430, ht_p150, 10000), + 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), +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0033-v6.0-regulator-qcom_spmi-add-support-for-HT_P600.patch b/target/linux/ipq60xx/patches-5.15/0033-v6.0-regulator-qcom_spmi-add-support-for-HT_P600.patch new file mode 100644 index 000000000..9648207c8 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0033-v6.0-regulator-qcom_spmi-add-support-for-HT_P600.patch @@ -0,0 +1,64 @@ +From 2ba036ce8e99673073adc5ac62e7768a47725567 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 4 Jul 2022 23:23:55 +0200 +Subject: [PATCH 33/44] regulator: qcom_spmi: add support for HT_P600 + +HT_P600 is a LDO PMOS regulator based on LV P600 using HFS430 layout +found in PMP8074 and PMS405 PMIC-s. + +Both PMP8074 and PMS405 define the programmable range as 1.704 to 1.896V +but the actual MAX output voltage depends on the exact LDO in each of +the PMIC-s. +Their usual voltage that they are used is 1.8V. + +It has a max current of 600mA, voltage step of 8mV. + +Signed-off-by: Robert Marko +Link: https://lore.kernel.org/r/20220704212402.1715182-5-robimarko@gmail.com +Signed-off-by: Mark Brown +--- + drivers/regulator/qcom_spmi-regulator.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c +index 49e5fe168391..828e97b873df 100644 +--- 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_HT_P150 = 0x35, ++ SPMI_REGULATOR_SUBTYPE_HT_P600 = 0x3d, + }; + + enum spmi_common_regulator_registers { +@@ -549,6 +550,10 @@ static struct spmi_voltage_range ht_p150_ranges[] = { + SPMI_VOLTAGE_RANGE(0, 1616000, 1616000, 3304000, 3304000, 8000), + }; + ++static struct spmi_voltage_range ht_p600_ranges[] = { ++ SPMI_VOLTAGE_RANGE(0, 1704000, 1704000, 1896000, 1896000, 8000), ++}; ++ + static DEFINE_SPMI_SET_POINTS(pldo); + static DEFINE_SPMI_SET_POINTS(nldo1); + static DEFINE_SPMI_SET_POINTS(nldo2); +@@ -570,6 +575,7 @@ static DEFINE_SPMI_SET_POINTS(ht_lvpldo); + static DEFINE_SPMI_SET_POINTS(ht_nldo); + static DEFINE_SPMI_SET_POINTS(hfs430); + static DEFINE_SPMI_SET_POINTS(ht_p150); ++static DEFINE_SPMI_SET_POINTS(ht_p600); + + static inline int spmi_vreg_read(struct spmi_regulator *vreg, u16 addr, u8 *buf, + int len) +@@ -1464,6 +1470,7 @@ static const struct regulator_ops spmi_hfs430_ops = { + + static const struct spmi_regulator_mapping supported_regulators[] = { + /* type subtype dig_min dig_max ltype ops setpoints hpm_min */ ++ SPMI_VREG(LDO, HT_P600, 0, INF, HFS430, hfs430, ht_p600, 10000), + SPMI_VREG(LDO, HT_P150, 0, INF, HFS430, hfs430, ht_p150, 10000), + SPMI_VREG(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000), + SPMI_VREG(BUCK, HFS430, 0, INF, HFS430, hfs430, hfs430, 10000), +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0034-v6.0-regulator-qcom_spmi-add-support-for-PMP8074-regulato.patch b/target/linux/ipq60xx/patches-5.15/0034-v6.0-regulator-qcom_spmi-add-support-for-PMP8074-regulato.patch new file mode 100644 index 000000000..a713dd9f9 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0034-v6.0-regulator-qcom_spmi-add-support-for-PMP8074-regulato.patch @@ -0,0 +1,74 @@ +From 933b687758646242fc7410edb06da70fe7540cdc Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 4 Jul 2022 23:23:57 +0200 +Subject: [PATCH 34/44] regulator: qcom_spmi: add support for PMP8074 + regulators + +PMP8074 is a companion PMIC for the Qualcomm IPQ8074 WiSoC-s. + +It features 5 HF-SMPS and 13 LDO regulators. + +HF-SMPS regulators are Buck HFS430 regulators. +L1, L2 and L3 are HT_N1200_ST subtype LDO regulators. +L4 is HT_N300_ST subtype LDO regulator. +L5 and L6 are HT_P600 subtype LDO regulators. +L7, L11, L12 and L13 are HT_P150 subtype LDO regulators. +L10 is HT_P50 subtype LDO regulator. + +This commit adds support for all of the buck regulators and LDO-s except +for L10 as I dont have documentation on its output voltage range. + +S3 is the CPU cluster voltage supply, S4 supplies the UBI32 NPU cores +and L11 is the SDIO/eMMC I/O voltage regulator required for high speeds. + +Signed-off-by: Robert Marko +Link: https://lore.kernel.org/r/20220704212402.1715182-7-robimarko@gmail.com +Signed-off-by: Mark Brown +--- + drivers/regulator/qcom_spmi-regulator.c | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c +index 828e97b873df..13521d47d589 100644 +--- a/drivers/regulator/qcom_spmi-regulator.c ++++ b/drivers/regulator/qcom_spmi-regulator.c +@@ -2101,6 +2101,28 @@ static const struct spmi_regulator_data pm8005_regulators[] = { + { } + }; + ++static const struct spmi_regulator_data pmp8074_regulators[] = { ++ { "s1", 0x1400, "vdd_s1"}, ++ { "s2", 0x1700, "vdd_s2"}, ++ { "s3", 0x1a00, "vdd_s3"}, ++ { "s4", 0x1d00, "vdd_s4"}, ++ { "s5", 0x2000, "vdd_s5"}, ++ { "l1", 0x4000, "vdd_l1_l2"}, ++ { "l2", 0x4100, "vdd_l1_l2"}, ++ { "l3", 0x4200, "vdd_l3_l8"}, ++ { "l4", 0x4300, "vdd_l4"}, ++ { "l5", 0x4400, "vdd_l5_l6_l15"}, ++ { "l6", 0x4500, "vdd_l5_l6_l15"}, ++ { "l7", 0x4600, "vdd_l7"}, ++ { "l8", 0x4700, "vdd_l3_l8"}, ++ { "l9", 0x4800, "vdd_l9"}, ++ /* l10 is currently unsupported HT_P50 */ ++ { "l11", 0x4a00, "vdd_l10_l11_l12_l13"}, ++ { "l12", 0x4b00, "vdd_l10_l11_l12_l13"}, ++ { "l13", 0x4c00, "vdd_l10_l11_l12_l13"}, ++ { } ++}; ++ + static const struct spmi_regulator_data pms405_regulators[] = { + { "s3", 0x1a00, "vdd_s3"}, + { } +@@ -2117,6 +2139,7 @@ static const struct of_device_id qcom_spmi_regulator_match[] = { + { .compatible = "qcom,pmi8994-regulators", .data = &pmi8994_regulators }, + { .compatible = "qcom,pm660-regulators", .data = &pm660_regulators }, + { .compatible = "qcom,pm660l-regulators", .data = &pm660l_regulators }, ++ { .compatible = "qcom,pmp8074-regulators", .data = &pmp8074_regulators }, + { .compatible = "qcom,pms405-regulators", .data = &pms405_regulators }, + { } + }; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0035-v6.0-pinctrl-qcom-pmic-gpio-add-support-for-PMP8074.patch b/target/linux/ipq60xx/patches-5.15/0035-v6.0-pinctrl-qcom-pmic-gpio-add-support-for-PMP8074.patch new file mode 100644 index 000000000..aa63a15f9 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0035-v6.0-pinctrl-qcom-pmic-gpio-add-support-for-PMP8074.patch @@ -0,0 +1,30 @@ +From 55545dc54f015387dccfb3c8fe20115c5f21b8a7 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 11 Jul 2022 22:34:05 +0200 +Subject: [PATCH 35/44] pinctrl: qcom-pmic-gpio: add support for PMP8074 + +PMP8074 has 12 GPIO-s with holes on GPIO1 and GPIO12. + +Signed-off-by: Robert Marko +Link: https://lore.kernel.org/r/20220711203408.2949888-4-robimarko@gmail.com +Signed-off-by: Linus Walleij +--- + drivers/pinctrl/qcom/pinctrl-spmi-gpio.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c +index b2562e893139..fe1d3b260e27 100644 +--- a/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c ++++ b/drivers/pinctrl/qcom/pinctrl-spmi-gpio.c +@@ -1167,6 +1167,8 @@ static const struct of_device_id pmic_gpio_of_match[] = { + { .compatible = "qcom,pmi8998-gpio", .data = (void *) 14 }, + { .compatible = "qcom,pmk8350-gpio", .data = (void *) 4 }, + { .compatible = "qcom,pmm8155au-gpio", .data = (void *) 10 }, ++ /* pmp8074 has 12 GPIOs with holes on 1 and 12 */ ++ { .compatible = "qcom,pmp8074-gpio", .data = (void *) 12 }, + { .compatible = "qcom,pmr735a-gpio", .data = (void *) 4 }, + { .compatible = "qcom,pmr735b-gpio", .data = (void *) 4 }, + /* pms405 has 12 GPIOs with holes on 1, 9, and 10 */ +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0036-v5.16-mfd-qcom-spmi-pmic-Sort-compatibles-in-the-driver.patch b/target/linux/ipq60xx/patches-5.15/0036-v5.16-mfd-qcom-spmi-pmic-Sort-compatibles-in-the-driver.patch new file mode 100644 index 000000000..b2913a9d0 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0036-v5.16-mfd-qcom-spmi-pmic-Sort-compatibles-in-the-driver.patch @@ -0,0 +1,65 @@ +From f76d65737cff6f41f0fab7a46a742d89c08fd652 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Sun, 17 Oct 2021 09:12:16 -0700 +Subject: [PATCH 36/44] mfd: qcom-spmi-pmic: Sort compatibles in the driver + +Sort the compatibles in the driver, to make it easier to validate that +the DT binding and driver are in sync. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Lee Jones +Link: https://lore.kernel.org/r/20211017161218.2378176-2-bjorn.andersson@linaro.org +--- + drivers/mfd/qcom-spmi-pmic.c | 30 +++++++++++++++--------------- + 1 file changed, 15 insertions(+), 15 deletions(-) + +diff --git a/drivers/mfd/qcom-spmi-pmic.c b/drivers/mfd/qcom-spmi-pmic.c +index a35d5cf16faa..8be07102a468 100644 +--- a/drivers/mfd/qcom-spmi-pmic.c ++++ b/drivers/mfd/qcom-spmi-pmic.c +@@ -40,27 +40,27 @@ + #define PM660_SUBTYPE 0x1B + + static const struct of_device_id pmic_spmi_id_table[] = { +- { .compatible = "qcom,spmi-pmic", .data = (void *)COMMON_SUBTYPE }, +- { .compatible = "qcom,pm8941", .data = (void *)PM8941_SUBTYPE }, +- { .compatible = "qcom,pm8841", .data = (void *)PM8841_SUBTYPE }, ++ { .compatible = "qcom,pm660", .data = (void *)PM660_SUBTYPE }, ++ { .compatible = "qcom,pm660l", .data = (void *)PM660L_SUBTYPE }, ++ { .compatible = "qcom,pm8004", .data = (void *)PM8004_SUBTYPE }, ++ { .compatible = "qcom,pm8005", .data = (void *)PM8005_SUBTYPE }, + { .compatible = "qcom,pm8019", .data = (void *)PM8019_SUBTYPE }, +- { .compatible = "qcom,pm8226", .data = (void *)PM8226_SUBTYPE }, + { .compatible = "qcom,pm8110", .data = (void *)PM8110_SUBTYPE }, +- { .compatible = "qcom,pma8084", .data = (void *)PMA8084_SUBTYPE }, +- { .compatible = "qcom,pmi8962", .data = (void *)PMI8962_SUBTYPE }, +- { .compatible = "qcom,pmd9635", .data = (void *)PMD9635_SUBTYPE }, +- { .compatible = "qcom,pm8994", .data = (void *)PM8994_SUBTYPE }, +- { .compatible = "qcom,pmi8994", .data = (void *)PMI8994_SUBTYPE }, +- { .compatible = "qcom,pm8916", .data = (void *)PM8916_SUBTYPE }, +- { .compatible = "qcom,pm8004", .data = (void *)PM8004_SUBTYPE }, ++ { .compatible = "qcom,pm8226", .data = (void *)PM8226_SUBTYPE }, ++ { .compatible = "qcom,pm8841", .data = (void *)PM8841_SUBTYPE }, + { .compatible = "qcom,pm8909", .data = (void *)PM8909_SUBTYPE }, ++ { .compatible = "qcom,pm8916", .data = (void *)PM8916_SUBTYPE }, ++ { .compatible = "qcom,pm8941", .data = (void *)PM8941_SUBTYPE }, + { .compatible = "qcom,pm8950", .data = (void *)PM8950_SUBTYPE }, +- { .compatible = "qcom,pmi8950", .data = (void *)PMI8950_SUBTYPE }, ++ { .compatible = "qcom,pm8994", .data = (void *)PM8994_SUBTYPE }, + { .compatible = "qcom,pm8998", .data = (void *)PM8998_SUBTYPE }, ++ { .compatible = "qcom,pma8084", .data = (void *)PMA8084_SUBTYPE }, ++ { .compatible = "qcom,pmd9635", .data = (void *)PMD9635_SUBTYPE }, ++ { .compatible = "qcom,pmi8950", .data = (void *)PMI8950_SUBTYPE }, ++ { .compatible = "qcom,pmi8962", .data = (void *)PMI8962_SUBTYPE }, ++ { .compatible = "qcom,pmi8994", .data = (void *)PMI8994_SUBTYPE }, + { .compatible = "qcom,pmi8998", .data = (void *)PMI8998_SUBTYPE }, +- { .compatible = "qcom,pm8005", .data = (void *)PM8005_SUBTYPE }, +- { .compatible = "qcom,pm660l", .data = (void *)PM660L_SUBTYPE }, +- { .compatible = "qcom,pm660", .data = (void *)PM660_SUBTYPE }, ++ { .compatible = "qcom,spmi-pmic", .data = (void *)COMMON_SUBTYPE }, + { } + }; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0037-v5.16-mfd-qcom-spmi-pmic-Add-missing-PMICs-supported-by-so.patch b/target/linux/ipq60xx/patches-5.15/0037-v5.16-mfd-qcom-spmi-pmic-Add-missing-PMICs-supported-by-so.patch new file mode 100644 index 000000000..19f6ec8ac --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0037-v5.16-mfd-qcom-spmi-pmic-Add-missing-PMICs-supported-by-so.patch @@ -0,0 +1,71 @@ +From b4f85660d09360b2ef9f04e51890fbf9935bf759 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Sun, 17 Oct 2021 09:12:18 -0700 +Subject: [PATCH 37/44] mfd: qcom-spmi-pmic: Add missing PMICs supported by + socinfo + +The Qualcomm socinfo driver has eight more PMICs described, add these to +the SPMI PMIC driver as well. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Lee Jones +Link: https://lore.kernel.org/r/20211017161218.2378176-4-bjorn.andersson@linaro.org +--- + drivers/mfd/qcom-spmi-pmic.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/mfd/qcom-spmi-pmic.c b/drivers/mfd/qcom-spmi-pmic.c +index 8be07102a468..1cacc00aa6c9 100644 +--- a/drivers/mfd/qcom-spmi-pmic.c ++++ b/drivers/mfd/qcom-spmi-pmic.c +@@ -31,6 +31,8 @@ + #define PM8916_SUBTYPE 0x0b + #define PM8004_SUBTYPE 0x0c + #define PM8909_SUBTYPE 0x0d ++#define PM8028_SUBTYPE 0x0e ++#define PM8901_SUBTYPE 0x0f + #define PM8950_SUBTYPE 0x10 + #define PMI8950_SUBTYPE 0x11 + #define PM8998_SUBTYPE 0x14 +@@ -38,6 +40,13 @@ + #define PM8005_SUBTYPE 0x18 + #define PM660L_SUBTYPE 0x1A + #define PM660_SUBTYPE 0x1B ++#define PM8150_SUBTYPE 0x1E ++#define PM8150L_SUBTYPE 0x1f ++#define PM8150B_SUBTYPE 0x20 ++#define PMK8002_SUBTYPE 0x21 ++#define PM8009_SUBTYPE 0x24 ++#define PM8150C_SUBTYPE 0x26 ++#define SMB2351_SUBTYPE 0x29 + + static const struct of_device_id pmic_spmi_id_table[] = { + { .compatible = "qcom,pm660", .data = (void *)PM660_SUBTYPE }, +@@ -45,9 +54,15 @@ static const struct of_device_id pmic_spmi_id_table[] = { + { .compatible = "qcom,pm8004", .data = (void *)PM8004_SUBTYPE }, + { .compatible = "qcom,pm8005", .data = (void *)PM8005_SUBTYPE }, + { .compatible = "qcom,pm8019", .data = (void *)PM8019_SUBTYPE }, ++ { .compatible = "qcom,pm8028", .data = (void *)PM8028_SUBTYPE }, + { .compatible = "qcom,pm8110", .data = (void *)PM8110_SUBTYPE }, ++ { .compatible = "qcom,pm8150", .data = (void *)PM8150_SUBTYPE }, ++ { .compatible = "qcom,pm8150b", .data = (void *)PM8150B_SUBTYPE }, ++ { .compatible = "qcom,pm8150c", .data = (void *)PM8150C_SUBTYPE }, ++ { .compatible = "qcom,pm8150l", .data = (void *)PM8150L_SUBTYPE }, + { .compatible = "qcom,pm8226", .data = (void *)PM8226_SUBTYPE }, + { .compatible = "qcom,pm8841", .data = (void *)PM8841_SUBTYPE }, ++ { .compatible = "qcom,pm8901", .data = (void *)PM8901_SUBTYPE }, + { .compatible = "qcom,pm8909", .data = (void *)PM8909_SUBTYPE }, + { .compatible = "qcom,pm8916", .data = (void *)PM8916_SUBTYPE }, + { .compatible = "qcom,pm8941", .data = (void *)PM8941_SUBTYPE }, +@@ -60,6 +75,8 @@ static const struct of_device_id pmic_spmi_id_table[] = { + { .compatible = "qcom,pmi8962", .data = (void *)PMI8962_SUBTYPE }, + { .compatible = "qcom,pmi8994", .data = (void *)PMI8994_SUBTYPE }, + { .compatible = "qcom,pmi8998", .data = (void *)PMI8998_SUBTYPE }, ++ { .compatible = "qcom,pmk8002", .data = (void *)PMK8002_SUBTYPE }, ++ { .compatible = "qcom,smb2351", .data = (void *)SMB2351_SUBTYPE }, + { .compatible = "qcom,spmi-pmic", .data = (void *)COMMON_SUBTYPE }, + { } + }; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0038-v6.0-iio-adc-qcom-spmi-adc5-add-ADC5_VREF_VADC-to-rev2-AD.patch b/target/linux/ipq60xx/patches-5.15/0038-v6.0-iio-adc-qcom-spmi-adc5-add-ADC5_VREF_VADC-to-rev2-AD.patch new file mode 100644 index 000000000..4efbdcbfe --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0038-v6.0-iio-adc-qcom-spmi-adc5-add-ADC5_VREF_VADC-to-rev2-AD.patch @@ -0,0 +1,32 @@ +From 1aa9a70ca9a1d3bb5139030fd9fc340d8525a7d7 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 19 Aug 2022 00:18:13 +0200 +Subject: [PATCH 38/44] iio: adc: qcom-spmi-adc5: add ADC5_VREF_VADC to rev2 + ADC5 + +Add support for ADC5_VREF_VADC channel to rev2 ADC5 channel list. +This channel measures the VADC reference LDO output. + +Signed-off-by: Robert Marko +Link: https://lore.kernel.org/r/20220818221815.346233-3-robimarko@gmail.com +Signed-off-by: Jonathan Cameron +--- + drivers/iio/adc/qcom-spmi-adc5.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/iio/adc/qcom-spmi-adc5.c b/drivers/iio/adc/qcom-spmi-adc5.c +index 87438d1e5c0b..7bd3745884f0 100644 +--- a/drivers/iio/adc/qcom-spmi-adc5.c ++++ b/drivers/iio/adc/qcom-spmi-adc5.c +@@ -589,6 +589,8 @@ static const struct adc5_channels adc5_chans_rev2[ADC5_MAX_CHANNEL] = { + SCALE_HW_CALIB_DEFAULT) + [ADC5_1P25VREF] = ADC5_CHAN_VOLT("vref_1p25", 0, + SCALE_HW_CALIB_DEFAULT) ++ [ADC5_VREF_VADC] = ADC5_CHAN_VOLT("vref_vadc", 0, ++ SCALE_HW_CALIB_DEFAULT) + [ADC5_VPH_PWR] = ADC5_CHAN_VOLT("vph_pwr", 1, + SCALE_HW_CALIB_DEFAULT) + [ADC5_VBAT_SNS] = ADC5_CHAN_VOLT("vbat_sns", 1, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0039-v6.0-phy-qcom-qmp-pcie-make-pipe-clock-rate-configurable.patch b/target/linux/ipq60xx/patches-5.15/0039-v6.0-phy-qcom-qmp-pcie-make-pipe-clock-rate-configurable.patch new file mode 100644 index 000000000..ee9e6c862 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0039-v6.0-phy-qcom-qmp-pcie-make-pipe-clock-rate-configurable.patch @@ -0,0 +1,52 @@ +From 8454872e52992108308e44aa974b441558fa1fc9 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 23 Aug 2022 22:43:51 +0200 +Subject: [PATCH 39/44] phy: qcom-qmp-pcie: make pipe clock rate configurable + +IPQ8074 Gen3 PCIe PHY uses 250MHz as the pipe clock rate instead of 125MHz +like every other PCIe QMP PHY does, so make it configurable as part of the +qmp_phy_cfg. + +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +Link: https://lore.kernel.org/r/20220621195512.1760362-1-robimarko@gmail.com +Signed-off-by: Vinod Koul +--- + drivers/phy/qualcomm/phy-qcom-qmp.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c +index ed69d455ac0e..4a0e8dd9c97d 100644 +--- a/drivers/phy/qualcomm/phy-qcom-qmp.c ++++ b/drivers/phy/qualcomm/phy-qcom-qmp.c +@@ -2842,6 +2842,9 @@ struct qmp_phy_cfg { + /* true, if PHY has secondary tx/rx lanes to be configured */ + bool is_dual_lane_phy; + ++ /* QMP PHY pipe clock interface rate */ ++ unsigned long pipe_clock_rate; ++ + /* true, if PCS block has no separate SW_RESET register */ + bool no_pcs_sw_reset; + }; +@@ -5138,8 +5141,15 @@ static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np) + + init.ops = &clk_fixed_rate_ops; + +- /* controllers using QMP phys use 125MHz pipe clock interface */ +- fixed->fixed_rate = 125000000; ++ /* ++ * Controllers using QMP PHY-s use 125MHz pipe clock interface ++ * unless other frequency is specified in the PHY config. ++ */ ++ if (qmp->phys[0]->cfg->pipe_clock_rate) ++ fixed->fixed_rate = qmp->phys[0]->cfg->pipe_clock_rate; ++ else ++ fixed->fixed_rate = 125000000; ++ + fixed->hw.init = &init; + + ret = devm_clk_hw_register(qmp->dev, &fixed->hw); +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0040-v6.0-phy-qcom-qmp-pcie-add-IPQ8074-PCIe-Gen3-QMP-PHY-supp.patch b/target/linux/ipq60xx/patches-5.15/0040-v6.0-phy-qcom-qmp-pcie-add-IPQ8074-PCIe-Gen3-QMP-PHY-supp.patch new file mode 100644 index 000000000..86019d5eb --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0040-v6.0-phy-qcom-qmp-pcie-add-IPQ8074-PCIe-Gen3-QMP-PHY-supp.patch @@ -0,0 +1,206 @@ +From 6702bed8d48e29bd51c4b702b0baf18c5b1814c1 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 23 Aug 2022 22:47:40 +0200 +Subject: [PATCH 40/44] phy: qcom-qmp-pcie: add IPQ8074 PCIe Gen3 QMP PHY + support + +IPQ8074 has 2 different single lane PCIe PHY-s, one Gen2 and one Gen3. +Gen2 one is already supported, so add the support for the Gen3 one. +It uses the same register layout as IPQ6018. + +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +Link: https://lore.kernel.org/r/20220621195512.1760362-3-robimarko@gmail.com +Signed-off-by: Vinod Koul +--- + drivers/phy/qualcomm/phy-qcom-qmp.c | 160 ++++++++++++++++++++++++++++ + 1 file changed, 160 insertions(+) + +diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c +index 4a0e8dd9c97d..0219a34f1047 100644 +--- a/drivers/phy/qualcomm/phy-qcom-qmp.c ++++ b/drivers/phy/qualcomm/phy-qcom-qmp.c +@@ -812,6 +812,133 @@ static const struct qmp_phy_init_tbl ipq8074_pcie_pcs_tbl[] = { + 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), +@@ -3167,6 +3294,36 @@ static const struct qmp_phy_cfg ipq8074_pciephy_cfg = { + .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 = ipq_pciephy_gen3_regs_layout, ++ ++ .start_ctrl = SERDES_START | PCS_START, ++ .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, ++ ++ .has_pwrdn_delay = true, ++ .pwrdn_delay_min = 995, /* us */ ++ .pwrdn_delay_max = 1005, /* us */ ++ ++ .pipe_clock_rate = 250000000, ++}; ++ + static const struct qmp_phy_cfg ipq6018_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 1, +@@ -5542,6 +5699,9 @@ static const struct of_device_id qcom_qmp_phy_of_match_table[] = { + }, { + .compatible = "qcom,ipq8074-qmp-pcie-phy", + .data = &ipq8074_pciephy_cfg, ++ }, { ++ .compatible = "qcom,ipq8074-qmp-gen3-pcie-phy", ++ .data = &ipq8074_pciephy_gen3_cfg, + }, { + .compatible = "qcom,ipq6018-qmp-pcie-phy", + .data = &ipq6018_pciephy_cfg, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0041-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch b/target/linux/ipq60xx/patches-5.15/0041-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch new file mode 100644 index 000000000..391930732 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0041-v5.16-soc-qcom-socinfo-Add-IPQ8074-family-ID-s.patch @@ -0,0 +1,55 @@ +From 70835efbc6c9dbc4e652aa60a250ecb1a2160a9b Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sun, 5 Sep 2021 19:11:31 +0200 +Subject: [PATCH 41/44] 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(+) + +diff --git a/drivers/soc/qcom/socinfo.c b/drivers/soc/qcom/socinfo.c +index 5beb452f2401..302a6a895c2f 100644 +--- 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" }, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0042-v6.0-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch b/target/linux/ipq60xx/patches-5.15/0042-v6.0-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch new file mode 100644 index 000000000..61f6f21c4 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0042-v6.0-PCI-dwc-tegra-move-GEN3_RELATED-DBI-register-to-comm.patch @@ -0,0 +1,52 @@ +From 66dafdaad281e0a0eb2045ffb1f8dcf72e25989f Mon Sep 17 00:00:00 2001 +From: Baruch Siach +Date: Mon, 7 Feb 2022 16:51:24 +0200 +Subject: [PATCH 42/44] 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(-) + +diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h +index 7d6e9b7576be..ea87809ee298 100644 +--- 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) + +diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c +index bdd84765e646..1381b00702fa 100644 +--- 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) +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0043-v6.0-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch b/target/linux/ipq60xx/patches-5.15/0043-v6.0-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch new file mode 100644 index 000000000..8e857a797 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0043-v6.0-PCI-qcom-Define-slot-capabilities-using-PCI_EXP_SLTC.patch @@ -0,0 +1,51 @@ +From 55299da8c17f23249497ee8868a5a268c6e3fbcc Mon Sep 17 00:00:00 2001 +From: Baruch Siach +Date: Mon, 7 Feb 2022 16:51:25 +0200 +Subject: [PATCH 43/44] 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(-) + +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index 4c599699e3c8..f49de615c9c4 100644 +--- 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 + +@@ -1125,7 +1136,7 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) + + 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; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0044-v6.0-PCI-qcom-Add-IPQ60xx-support.patch b/target/linux/ipq60xx/patches-5.15/0044-v6.0-PCI-qcom-Add-IPQ60xx-support.patch new file mode 100644 index 000000000..a5ec337c8 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0044-v6.0-PCI-qcom-Add-IPQ60xx-support.patch @@ -0,0 +1,219 @@ +From b9d02fcefdf166671356a08dd621429e63541b22 Mon Sep 17 00:00:00 2001 +From: Selvam Sathappan Periakaruppan +Date: Thu, 10 Feb 2022 18:02:47 +0100 +Subject: [PATCH 44/44] 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(+) + +diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h +index ea87809ee298..279c3778a13b 100644 +--- 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) +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index f49de615c9c4..f50de3408add 100644 +--- 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; +@@ -1276,6 +1286,121 @@ static void qcom_pcie_post_deinit_2_7_0(struct qcom_pcie *pcie) + 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); +@@ -1467,6 +1592,15 @@ static const struct qcom_pcie_ops ops_1_9_0 = { + .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, +@@ -1565,6 +1699,7 @@ static const struct of_device_id qcom_pcie_match[] = { + { .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 }, + { } + }; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0100-arm64-dts-ipq8074-add-reserved-memory-nodes.patch b/target/linux/ipq60xx/patches-5.15/0100-arm64-dts-ipq8074-add-reserved-memory-nodes.patch new file mode 100644 index 000000000..225b84c1d --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0100-arm64-dts-ipq8074-add-reserved-memory-nodes.patch @@ -0,0 +1,75 @@ +From 2dc8319c43ccc511a38f441ab4f7aa120af9a9fb Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Wed, 22 Dec 2021 12:23:34 +0100 +Subject: [PATCH 100/137] 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 | 35 +++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index d53675fc1595..5985dfff073d 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -85,6 +85,26 @@ reserved-memory { + #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>; ++ }; ++ + smem@4ab00000 { + compatible = "qcom,smem"; + reg = <0x0 0x4ab00000 0x0 0x00100000>; +@@ -97,6 +117,21 @@ memory@4ac00000 { + no-map; + reg = <0x0 0x4ac00000 0x0 0x00400000>; + }; ++ ++ 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 { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0101-clk-qcom-clk-rcg2-add-rcg2-mux-ops.patch b/target/linux/ipq60xx/patches-5.15/0101-clk-qcom-clk-rcg2-add-rcg2-mux-ops.patch new file mode 100644 index 000000000..2a991eced --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0101-clk-qcom-clk-rcg2-add-rcg2-mux-ops.patch @@ -0,0 +1,56 @@ +From 9d38e110e23ce0b858ccd67a8a819dc187529a33 Mon Sep 17 00:00:00 2001 +From: Christian Marangi +Date: Fri, 8 Jul 2022 23:24:25 +0200 +Subject: [PATCH 101/137] clk: qcom: clk-rcg2: add rcg2 mux ops + +An RCG may act as a mux that switch between 2 parents. +This is the case on IPQ6018 and IPQ8074 where the APCS core clk that feeds +the CPU cluster clock just switches between XO and the PLL that feeds it. + +Add the required ops to add support for this special configuration and use +the generic mux function to determine the rate. + +This way we dont have to keep a essentially dummy frequency table to use +RCG2 as a mux. + +Signed-off-by: Christian Marangi +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +--- + drivers/clk/qcom/clk-rcg.h | 1 + + drivers/clk/qcom/clk-rcg2.c | 7 +++++++ + 2 files changed, 8 insertions(+) + +diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h +index 99efcc7f8d88..248115a018bc 100644 +--- a/drivers/clk/qcom/clk-rcg.h ++++ b/drivers/clk/qcom/clk-rcg.h +@@ -164,6 +164,7 @@ struct clk_rcg2_gfx3d { + + extern const struct clk_ops clk_rcg2_ops; + extern const struct clk_ops clk_rcg2_floor_ops; ++extern const struct clk_ops clk_rcg2_mux_closest_ops; + extern const struct clk_ops clk_edp_pixel_ops; + extern const struct clk_ops clk_byte_ops; + extern const struct clk_ops clk_byte2_ops; +diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c +index c3823cc32edc..fdbbda1f2ba4 100644 +--- a/drivers/clk/qcom/clk-rcg2.c ++++ b/drivers/clk/qcom/clk-rcg2.c +@@ -477,6 +477,13 @@ const struct clk_ops clk_rcg2_floor_ops = { + }; + EXPORT_SYMBOL_GPL(clk_rcg2_floor_ops); + ++const struct clk_ops clk_rcg2_mux_closest_ops = { ++ .determine_rate = __clk_mux_determine_rate_closest, ++ .get_parent = clk_rcg2_get_parent, ++ .set_parent = clk_rcg2_set_parent, ++}; ++EXPORT_SYMBOL_GPL(clk_rcg2_mux_closest_ops); ++ + struct frac_entry { + int num; + int den; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0102-clk-qcom-apss-ipq6018-fix-apcs_alias0_clk_src.patch b/target/linux/ipq60xx/patches-5.15/0102-clk-qcom-apss-ipq6018-fix-apcs_alias0_clk_src.patch new file mode 100644 index 000000000..301a04101 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0102-clk-qcom-apss-ipq6018-fix-apcs_alias0_clk_src.patch @@ -0,0 +1,66 @@ +From d2b31da4eae2175ff86f28f596b54abde08d382f Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 9 Jul 2022 00:18:45 +0200 +Subject: [PATCH 102/137] clk: qcom: apss-ipq6018: fix apcs_alias0_clk_src + +While working on IPQ8074 APSS driver it was discovered that IPQ6018 and +IPQ8074 use almost the same PLL and APSS clocks, however APSS driver is +currently broken. + +More precisely apcs_alias0_clk_src is broken, it was added as regmap_mux +clock. +However after debugging why it was always stuck at 800Mhz, it was figured +out that its not regmap_mux compatible at all. +It is a simple mux but it uses RCG2 register layout and control bits, so +utilize the new clk_rcg2_mux_closest_ops to correctly drive it while not +having to provide a dummy frequency table. + +While we are here, use ARRAY_SIZE for number of parents. + +Tested on IPQ6018-CP01-C1 reference board and multiple IPQ8074 boards. + +Fixes: 5e77b4ef1b19 ("clk: qcom: Add ipq6018 apss clock controller") +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +--- + drivers/clk/qcom/apss-ipq6018.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/drivers/clk/qcom/apss-ipq6018.c b/drivers/clk/qcom/apss-ipq6018.c +index d78ff2f310bf..be952d417ded 100644 +--- a/drivers/clk/qcom/apss-ipq6018.c ++++ b/drivers/clk/qcom/apss-ipq6018.c +@@ -16,7 +16,7 @@ + #include "clk-regmap.h" + #include "clk-branch.h" + #include "clk-alpha-pll.h" +-#include "clk-regmap-mux.h" ++#include "clk-rcg.h" + + enum { + P_XO, +@@ -33,16 +33,15 @@ static const struct parent_map parents_apcs_alias0_clk_src_map[] = { + { P_APSS_PLL_EARLY, 5 }, + }; + +-static struct clk_regmap_mux apcs_alias0_clk_src = { +- .reg = 0x0050, +- .width = 3, +- .shift = 7, ++static struct clk_rcg2 apcs_alias0_clk_src = { ++ .cmd_rcgr = 0x0050, ++ .hid_width = 5, + .parent_map = parents_apcs_alias0_clk_src_map, + .clkr.hw.init = &(struct clk_init_data){ + .name = "apcs_alias0_clk_src", + .parent_data = parents_apcs_alias0_clk_src, +- .num_parents = 2, +- .ops = &clk_regmap_mux_closest_ops, ++ .num_parents = ARRAY_SIZE(parents_apcs_alias0_clk_src), ++ .ops = &clk_rcg2_mux_closest_ops, + .flags = CLK_SET_RATE_PARENT, + }, + }; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0103-clk-qcom-apss-ipq6018-mark-apcs_alias0_core_clk-as-c.patch b/target/linux/ipq60xx/patches-5.15/0103-clk-qcom-apss-ipq6018-mark-apcs_alias0_core_clk-as-c.patch new file mode 100644 index 000000000..9fb3cd41f --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0103-clk-qcom-apss-ipq6018-mark-apcs_alias0_core_clk-as-c.patch @@ -0,0 +1,36 @@ +From 4f9fcc72d6b5d64f8591f179f222154139eb1c72 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 9 Jul 2022 00:26:54 +0200 +Subject: [PATCH 103/137] clk: qcom: apss-ipq6018: mark apcs_alias0_core_clk as + critical + +While fixing up the driver I noticed that my IPQ8074 board was hanging +after CPUFreq switched the frequency during boot, WDT would eventually +reset it. + +So mark apcs_alias0_core_clk as critical since its the clock feeding the +CPU cluster and must never be disabled. + +Fixes: 5e77b4ef1b19 ("clk: qcom: Add ipq6018 apss clock controller") +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +--- + drivers/clk/qcom/apss-ipq6018.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/clk/qcom/apss-ipq6018.c b/drivers/clk/qcom/apss-ipq6018.c +index be952d417ded..f2f502e2d5a4 100644 +--- a/drivers/clk/qcom/apss-ipq6018.c ++++ b/drivers/clk/qcom/apss-ipq6018.c +@@ -56,7 +56,7 @@ static struct clk_branch apcs_alias0_core_clk = { + .parent_hws = (const struct clk_hw *[]){ + &apcs_alias0_clk_src.clkr.hw }, + .num_parents = 1, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + .ops = &clk_branch2_ops, + }, + }, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0104-clk-qcom-apss-ipq-pll-use-OF-match-data-for-Alpha-PL.patch b/target/linux/ipq60xx/patches-5.15/0104-clk-qcom-apss-ipq-pll-use-OF-match-data-for-Alpha-PL.patch new file mode 100644 index 000000000..6de4a4223 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0104-clk-qcom-apss-ipq-pll-use-OF-match-data-for-Alpha-PL.patch @@ -0,0 +1,73 @@ +From 8878f39722eeacbb40babe82ad763d8d20214018 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 9 Jul 2022 00:32:04 +0200 +Subject: [PATCH 104/137] clk: qcom: apss-ipq-pll: use OF match data for Alpha + PLL config + +Convert the driver to use OF match data for providing the Alpha PLL config +per compatible. +This is required for IPQ8074 support since it uses a different Alpha PLL +config. + +While we are here rename "ipq_pll_config" to "ipq6018_pll_config" to make +it clear that it is for IPQ6018 only. + +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/apss-ipq-pll.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/drivers/clk/qcom/apss-ipq-pll.c b/drivers/clk/qcom/apss-ipq-pll.c +index bef7899ad0d6..ba77749b16c4 100644 +--- a/drivers/clk/qcom/apss-ipq-pll.c ++++ b/drivers/clk/qcom/apss-ipq-pll.c +@@ -2,6 +2,7 @@ + // Copyright (c) 2018, The Linux Foundation. All rights reserved. + #include + #include ++#include + #include + #include + +@@ -36,7 +37,7 @@ static struct clk_alpha_pll ipq_pll = { + }, + }; + +-static const struct alpha_pll_config ipq_pll_config = { ++static const struct alpha_pll_config ipq6018_pll_config = { + .l = 0x37, + .config_ctl_val = 0x04141200, + .config_ctl_hi_val = 0x0, +@@ -54,6 +55,7 @@ static const struct regmap_config ipq_pll_regmap_config = { + + static int apss_ipq_pll_probe(struct platform_device *pdev) + { ++ const struct alpha_pll_config *ipq_pll_config; + struct device *dev = &pdev->dev; + struct regmap *regmap; + void __iomem *base; +@@ -67,7 +69,11 @@ static int apss_ipq_pll_probe(struct platform_device *pdev) + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + +- clk_alpha_pll_configure(&ipq_pll, regmap, &ipq_pll_config); ++ ipq_pll_config = of_device_get_match_data(&pdev->dev); ++ if (!ipq_pll_config) ++ return -ENODEV; ++ ++ clk_alpha_pll_configure(&ipq_pll, regmap, ipq_pll_config); + + ret = devm_clk_register_regmap(dev, &ipq_pll.clkr); + if (ret) +@@ -78,7 +84,7 @@ static int apss_ipq_pll_probe(struct platform_device *pdev) + } + + static const struct of_device_id apss_ipq_pll_match_table[] = { +- { .compatible = "qcom,ipq6018-a53pll" }, ++ { .compatible = "qcom,ipq6018-a53pll", .data = &ipq6018_pll_config }, + { } + }; + MODULE_DEVICE_TABLE(of, apss_ipq_pll_match_table); +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0105-clk-qcom-apss-ipq-pll-update-IPQ6018-Alpha-PLL-confi.patch b/target/linux/ipq60xx/patches-5.15/0105-clk-qcom-apss-ipq-pll-update-IPQ6018-Alpha-PLL-confi.patch new file mode 100644 index 000000000..46854e53c --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0105-clk-qcom-apss-ipq-pll-update-IPQ6018-Alpha-PLL-confi.patch @@ -0,0 +1,43 @@ +From 426edd7e45e9eaf18c433739ceeb51e6f2f8e190 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 11 Jul 2022 22:40:52 +0200 +Subject: [PATCH 105/137] clk: qcom: apss-ipq-pll: update IPQ6018 Alpha PLL + config + +Update the IPQ6018 Alpha PLL config to the latest one from the downstream +5.4 kernel[1]. + +This one should match the production SoC-s. + +Tested on IPQ6018 CP01-C1 reference board. + +[1] https://git.codelinaro.org/clo/qsdk/oss/kernel/linux-ipq-5.4/-/blob/NHSS.QSDK.12.1.r4/drivers/clk/qcom/apss-ipq-pll.c#L41 +Signed-off-by: Robert Marko +--- + drivers/clk/qcom/apss-ipq-pll.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/drivers/clk/qcom/apss-ipq-pll.c b/drivers/clk/qcom/apss-ipq-pll.c +index ba77749b16c4..a4016f3854c2 100644 +--- a/drivers/clk/qcom/apss-ipq-pll.c ++++ b/drivers/clk/qcom/apss-ipq-pll.c +@@ -39,10 +39,14 @@ static struct clk_alpha_pll ipq_pll = { + + static const struct alpha_pll_config ipq6018_pll_config = { + .l = 0x37, +- .config_ctl_val = 0x04141200, +- .config_ctl_hi_val = 0x0, ++ .config_ctl_val = 0x240d4828, ++ .config_ctl_hi_val = 0x6, + .early_output_mask = BIT(3), ++ .aux2_output_mask = BIT(2), ++ .aux_output_mask = BIT(1), + .main_output_mask = BIT(0), ++ .test_ctl_val = 0x1c0000C0, ++ .test_ctl_hi_val = 0x4000, + }; + + static const struct regmap_config ipq_pll_regmap_config = { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0106-clk-qcom-apss-ipq-pll-add-support-for-IPQ8074.patch b/target/linux/ipq60xx/patches-5.15/0106-clk-qcom-apss-ipq-pll-add-support-for-IPQ8074.patch new file mode 100644 index 000000000..0e5fdddb4 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0106-clk-qcom-apss-ipq-pll-add-support-for-IPQ8074.patch @@ -0,0 +1,56 @@ +From c633afe32123157370f21aeaf3d705ca584fc754 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 11 Jul 2022 14:23:08 +0200 +Subject: [PATCH 106/137] clk: qcom: apss-ipq-pll: add support for IPQ8074 + +Add support for IPQ8074 since it uses the same PLL setup, however it uses +slightly different Alpha PLL config. + +Alpha PLL config was obtained by dumping PLL registers from a running +device. + +Signed-off-by: Robert Marko +--- +Changes in v2: +* Drop hardcoded compatible check for IPQ6018 to do the PLL config and +utilize match data provided by previous commit +* Add IPQ8074 Alpha PLL config using match data +* Update commit description to reflect changes +--- + drivers/clk/qcom/apss-ipq-pll.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/drivers/clk/qcom/apss-ipq-pll.c b/drivers/clk/qcom/apss-ipq-pll.c +index a4016f3854c2..a5aea27eb867 100644 +--- a/drivers/clk/qcom/apss-ipq-pll.c ++++ b/drivers/clk/qcom/apss-ipq-pll.c +@@ -49,6 +49,18 @@ static const struct alpha_pll_config ipq6018_pll_config = { + .test_ctl_hi_val = 0x4000, + }; + ++static const struct alpha_pll_config ipq8074_pll_config = { ++ .l = 0x48, ++ .config_ctl_val = 0x200d4828, ++ .config_ctl_hi_val = 0x6, ++ .early_output_mask = BIT(3), ++ .aux2_output_mask = BIT(2), ++ .aux_output_mask = BIT(1), ++ .main_output_mask = BIT(0), ++ .test_ctl_val = 0x1c000000, ++ .test_ctl_hi_val = 0x4000, ++}; ++ + static const struct regmap_config ipq_pll_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, +@@ -89,6 +101,7 @@ static int apss_ipq_pll_probe(struct platform_device *pdev) + + static const struct of_device_id apss_ipq_pll_match_table[] = { + { .compatible = "qcom,ipq6018-a53pll", .data = &ipq6018_pll_config }, ++ { .compatible = "qcom,ipq8074-a53pll", .data = &ipq8074_pll_config }, + { } + }; + MODULE_DEVICE_TABLE(of, apss_ipq_pll_match_table); +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0107-arm64-dts-qcom-ipq8074-correct-APCS-register-space-s.patch b/target/linux/ipq60xx/patches-5.15/0107-arm64-dts-qcom-ipq8074-correct-APCS-register-space-s.patch new file mode 100644 index 000000000..8bf69e7e2 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0107-arm64-dts-qcom-ipq8074-correct-APCS-register-space-s.patch @@ -0,0 +1,36 @@ +From f3d524334069e69554eaecd8adf75284dff7c9d9 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 18 Aug 2022 23:15:43 +0200 +Subject: [PATCH 107/137] arm64: dts: qcom: ipq8074: correct APCS register + space size + +APCS DTS addition that was merged, was not supposed to get merged as it +was part of patch series that was superseded by 2 more patch series +that resolved issues with this one and greatly simplified things. + +Since it already got merged, start by correcting the register space +size as APCS will not be providing regmap for PLL and it will conflict +with the standalone A53 PLL node. + +Fixes: 50ed9fffec3a ("arm64: dts: qcom: ipq8074: add APCS node") +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 5985dfff073d..5b62d7590fc6 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -703,7 +703,7 @@ watchdog: watchdog@b017000 { + + apcs_glb: mailbox@b111000 { + compatible = "qcom,ipq8074-apcs-apps-global"; +- reg = <0x0b111000 0x6000>; ++ reg = <0x0b111000 0x1000>; + + #clock-cells = <1>; + #mbox-cells = <1>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0108-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch b/target/linux/ipq60xx/patches-5.15/0108-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch new file mode 100644 index 000000000..25b02e3e3 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0108-arm64-dts-qcom-ipq8074-add-A53-PLL-node.patch @@ -0,0 +1,35 @@ +From be028f5f79b8af6ea16ffeea486e216acdf80789 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 18 Aug 2022 23:21:06 +0200 +Subject: [PATCH 108/137] arm64: dts: qcom: ipq8074: add A53 PLL node + +Add the required node for A53 PLL which will be used to provide the CPU +clock via APCS for APSS scaling. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 5b62d7590fc6..bace14b742a1 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -709,6 +709,14 @@ apcs_glb: mailbox@b111000 { + #mbox-cells = <1>; + }; + ++ a53pll: clock@b116000 { ++ compatible = "qcom,ipq8074-a53pll"; ++ reg = <0x0b116000 0x40>; ++ #clock-cells = <0>; ++ clocks = <&xo>; ++ clock-names = "xo"; ++ }; ++ + timer@b120000 { + #address-cells = <1>; + #size-cells = <1>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0109-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-support.patch b/target/linux/ipq60xx/patches-5.15/0109-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-support.patch new file mode 100644 index 000000000..a7899dd1c --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0109-mailbox-qcom-apcs-ipc-add-IPQ8074-APSS-clock-support.patch @@ -0,0 +1,55 @@ +From ded0538937e9edf8b217d2082fd30af3bf7bd10b Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 28 Dec 2021 20:59:18 +0100 +Subject: [PATCH 109/137] mailbox: qcom-apcs-ipc: add IPQ8074 APSS clock + support + +IPQ8074 has the APSS clock controller utilizing the same register space as +the APCS, so provide access to the APSS utilizing a child device like +IPQ6018. + +IPQ6018 and IPQ8074 use the same controller and driver, so just utilize +IPQ6018 match data for IPQ8074. + +Signed-off-by: Robert Marko +Reviewed-by: Dmitry Baryshkov +--- +Changes in v7: +* Dont max_register modifications +* Drop custom IPQ8074 match data and use IPQ6018 one as they share the +controller and driver + +Changes in v5: +* Use lower case hex for max_register +* Update the APSS clock name to match the new one without commas +--- + drivers/mailbox/qcom-apcs-ipc-mailbox.c | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/drivers/mailbox/qcom-apcs-ipc-mailbox.c b/drivers/mailbox/qcom-apcs-ipc-mailbox.c +index 82ccfaf14b24..0d8d1b17ba45 100644 +--- a/drivers/mailbox/qcom-apcs-ipc-mailbox.c ++++ b/drivers/mailbox/qcom-apcs-ipc-mailbox.c +@@ -33,10 +33,6 @@ static const struct qcom_apcs_ipc_data ipq6018_apcs_data = { + .offset = 8, .clk_name = "qcom,apss-ipq6018-clk" + }; + +-static const struct qcom_apcs_ipc_data ipq8074_apcs_data = { +- .offset = 8, .clk_name = NULL +-}; +- + static const struct qcom_apcs_ipc_data msm8916_apcs_data = { + .offset = 8, .clk_name = "qcom-apcs-msm8916-clk" + }; +@@ -160,7 +156,7 @@ static int qcom_apcs_ipc_remove(struct platform_device *pdev) + /* .data is the offset of the ipc register within the global block */ + static const struct of_device_id qcom_apcs_ipc_of_match[] = { + { .compatible = "qcom,ipq6018-apcs-apps-global", .data = &ipq6018_apcs_data }, +- { .compatible = "qcom,ipq8074-apcs-apps-global", .data = &ipq8074_apcs_data }, ++ { .compatible = "qcom,ipq8074-apcs-apps-global", .data = &ipq6018_apcs_data }, + { .compatible = "qcom,msm8916-apcs-kpss-global", .data = &msm8916_apcs_data }, + { .compatible = "qcom,msm8939-apcs-kpss-global", .data = &msm8916_apcs_data }, + { .compatible = "qcom,msm8953-apcs-kpss-global", .data = &msm8994_apcs_data }, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0110-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch b/target/linux/ipq60xx/patches-5.15/0110-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch new file mode 100644 index 000000000..331b3c79a --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0110-arm64-dts-qcom-ipq8074-add-clocks-to-APCS.patch @@ -0,0 +1,34 @@ +From 0a36a586424feabf9ce9436379f9d061b7844155 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 18 Aug 2022 23:25:00 +0200 +Subject: [PATCH 110/137] arm64: dts: qcom: ipq8074: add clocks to APCS + +APCS now has support for providing the APSS clocks as the child device +for IPQ8074. + +So, add the A53 PLL and XO clocks in order to use APCS as the CPU +clocksource for APSS scaling. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index bace14b742a1..7d4ff7d8a239 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -704,8 +704,9 @@ watchdog: watchdog@b017000 { + apcs_glb: mailbox@b111000 { + compatible = "qcom,ipq8074-apcs-apps-global"; + reg = <0x0b111000 0x1000>; +- + #clock-cells = <1>; ++ clocks = <&a53pll>, <&xo>; ++ clock-names = "pll", "xo"; + #mbox-cells = <1>; + }; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0111-PCI-qcom-add-IPQ8074-Gen3-support.patch b/target/linux/ipq60xx/patches-5.15/0111-PCI-qcom-add-IPQ8074-Gen3-support.patch new file mode 100644 index 000000000..afa76389e --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0111-PCI-qcom-add-IPQ8074-Gen3-support.patch @@ -0,0 +1,50 @@ +From f086c5659cd54946b618ae4c695a8c05096f267a Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 20 Dec 2021 15:01:36 +0100 +Subject: [PATCH 111/137] 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(-) + +diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c +index f50de3408add..5aec3510b5c9 100644 +--- 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; + }; + +@@ -1296,8 +1296,10 @@ static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie) + 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) +@@ -1700,6 +1702,7 @@ static const struct of_device_id qcom_pcie_match[] = { + { .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 }, + { } + }; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0112-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch b/target/linux/ipq60xx/patches-5.15/0112-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch new file mode 100644 index 000000000..9f2aa643e --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0112-arm64-dts-ipq8074-fix-PCI-related-DT-nodes.patch @@ -0,0 +1,221 @@ +From 184c9b73285f05ebc013205d54ed11cd968cb38e Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 20 Dec 2021 15:08:04 +0100 +Subject: [PATCH 112/137] 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 | 93 +++++++++++++++------------ + 1 file changed, 52 insertions(+), 41 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 7d4ff7d8a239..019c8e302273 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -232,59 +232,61 @@ qusb_phy_0: phy@79000 { + status = "disabled"; + }; + +- pcie_qmp0: phy@86000 { +- compatible = "qcom,ipq8074-qmp-pcie-phy"; +- reg = <0x00086000 0x1000>; ++ pcie_qmp0: phy@84000 { ++ compatible = "qcom,ipq8074-qmp-gen3-pcie-phy"; ++ reg = <0x00084000 0x1bc>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + clocks = <&gcc GCC_PCIE0_AUX_CLK>, +- <&gcc GCC_PCIE0_AHB_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"; ++ <&gcc GCC_PCIE0PHY_PHY_BCR>; ++ reset-names = "phy", "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-names = "gcc_pcie0_pipe_clk_src"; + }; + }; + + pcie_qmp1: phy@8e000 { + compatible = "qcom,ipq8074-qmp-pcie-phy"; +- reg = <0x0008e000 0x1000>; ++ reg = <0x0008e000 0x1c4>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + clocks = <&gcc GCC_PCIE1_AUX_CLK>, +- <&gcc GCC_PCIE1_AHB_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"; ++ <&gcc GCC_PCIE1PHY_PHY_BCR>; ++ reset-names = "phy", "common"; ++ + 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-names = "gcc_pcie1_pipe_clk_src"; + }; + }; + +@@ -686,7 +688,7 @@ intc: interrupt-controller@b000000 { + 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>; +@@ -787,6 +789,7 @@ pcie1: pci@10000000 { + linux,pci-domain = <1>; + bus-range = <0x00 0xff>; + num-lanes = <1>; ++ max-link-speed = <2>; + #address-cells = <3>; + #size-cells = <2>; + +@@ -794,12 +797,12 @@ pcie1: pci@10000000 { + 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 +@@ -839,16 +842,18 @@ IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + }; + + 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>; + +@@ -856,12 +861,12 @@ pcie0: pci@20000000 { + 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 +@@ -877,27 +882,33 @@ IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + <&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"; + }; + }; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0113-remoteproc-qcom-Add-PRNG-proxy-clock.patch b/target/linux/ipq60xx/patches-5.15/0113-remoteproc-qcom-Add-PRNG-proxy-clock.patch new file mode 100644 index 000000000..8251f71d4 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0113-remoteproc-qcom-Add-PRNG-proxy-clock.patch @@ -0,0 +1,160 @@ +From 84e13a5267e43bb0a6a1f764211fda7769dc9cbe Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:05 +0530 +Subject: [PATCH 113/137] 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(-) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 20d50ec7eff1..0e5484020296 100644 +--- 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 *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 *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(struct q6v5_wcss *wcss) + 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 q6v5_wcss *wcss) + 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 platform_device *pdev) + 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 platform_device *pdev) + } + + 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_ipq8074_res_init = { + }; + + 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, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0114-remoteproc-qcom-Add-secure-PIL-support.patch b/target/linux/ipq60xx/patches-5.15/0114-remoteproc-qcom-Add-secure-PIL-support.patch new file mode 100644 index 000000000..ac329d847 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0114-remoteproc-qcom-Add-secure-PIL-support.patch @@ -0,0 +1,148 @@ +From cb3b9e284104fd7fe4aa92a37df005577aed2c40 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:06 +0530 +Subject: [PATCH 114/137] 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(-) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 0e5484020296..7d173b7816b8 100644 +--- 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 *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 *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 *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 *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 *rproc, const struct firmware *fw) + 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 platform_device *pdev) + 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 platform_device *pdev) + + 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_ipq8074_res_init = { + .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 = { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0115-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch b/target/linux/ipq60xx/patches-5.15/0115-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch new file mode 100644 index 000000000..20e7295b7 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0115-remoteproc-qcom-Add-support-for-split-q6-m3-wlan-fir.patch @@ -0,0 +1,109 @@ +From bcb2c37f265924ac43642f1f97c964dd546b3cb5 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:07 +0530 +Subject: [PATCH 115/137] 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(-) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 7d173b7816b8..60ed0c046693 100644 +--- 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 rproc *rproc, u64 da, size_t len, bool *i + 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 platform_device *pdev) + 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 platform_device *pdev) + 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 platform_device *pdev) + + 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_qcs404_res_init = { + .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, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0116-remoteproc-qcom-Add-ssr-subdevice-identifier.patch b/target/linux/ipq60xx/patches-5.15/0116-remoteproc-qcom-Add-ssr-subdevice-identifier.patch new file mode 100644 index 000000000..05c29d6bd --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0116-remoteproc-qcom-Add-ssr-subdevice-identifier.patch @@ -0,0 +1,29 @@ +From 5b717749ce49853f495ebac227c72013622b0810 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:08 +0530 +Subject: [PATCH 116/137] 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(+) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 60ed0c046693..e32efdc660d2 100644 +--- a/drivers/remoteproc/qcom_q6v5_wcss.c ++++ b/drivers/remoteproc/qcom_q6v5_wcss.c +@@ -1174,6 +1174,7 @@ static const struct wcss_data wcss_ipq8074_res_init = { + .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, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0117-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch b/target/linux/ipq60xx/patches-5.15/0117-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch new file mode 100644 index 000000000..c91e3817a --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0117-remoteproc-qcom-Update-regmap-offsets-for-halt-regis.patch @@ -0,0 +1,85 @@ +From 3bc5b97ecbb003e413ae76b332b0ccdba05ef6bc Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:09 +0530 +Subject: [PATCH 117/137] 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(-) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index e32efdc660d2..16fc5a33adaf 100644 +--- 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 q6v5_wcss *wcss, + } + } + +- 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 q6v5_wcss *wcss, + 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_ipq8074_res_init = { + .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_qcs404_res_init = { + .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, +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0118-drivers-thermal-tsens-Add-support-for-combined-inter.patch b/target/linux/ipq60xx/patches-5.15/0118-drivers-thermal-tsens-Add-support-for-combined-inter.patch new file mode 100644 index 000000000..5b8c5e86a --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0118-drivers-thermal-tsens-Add-support-for-combined-inter.patch @@ -0,0 +1,153 @@ +From 24a47e4619d90266188d26be04c0c29854294f06 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 28 Apr 2022 14:58:16 +0200 +Subject: [PATCH 118/137] drivers: thermal: tsens: Add support for combined + interrupt + +Despite using tsens v2.3 IP, IPQ8074 and IPQ6018 only have one IRQ for +signaling both up/low and critical trips. + +Signed-off-by: Robert Marko +--- +Changes in v7: +* Rebase to apply on next-20220818 + +Changes in v6: +* Check critical IRQ handler return, simplify up/low return +--- + drivers/thermal/qcom/tsens-8960.c | 1 + + drivers/thermal/qcom/tsens-v0_1.c | 1 + + drivers/thermal/qcom/tsens-v1.c | 1 + + drivers/thermal/qcom/tsens-v2.c | 1 + + drivers/thermal/qcom/tsens.c | 38 ++++++++++++++++++++++++++----- + drivers/thermal/qcom/tsens.h | 2 ++ + 6 files changed, 38 insertions(+), 6 deletions(-) + +diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c +index 67c1748cdf73..ee584e5b07e5 100644 +--- a/drivers/thermal/qcom/tsens-8960.c ++++ b/drivers/thermal/qcom/tsens-8960.c +@@ -269,6 +269,7 @@ static const struct tsens_ops ops_8960 = { + static struct tsens_features tsens_8960_feat = { + .ver_major = VER_0, + .crit_int = 0, ++ .combo_int = 0, + .adc = 1, + .srot_split = 0, + .max_sensors = 11, +diff --git a/drivers/thermal/qcom/tsens-v0_1.c b/drivers/thermal/qcom/tsens-v0_1.c +index f136cb350238..6effb822bf3c 100644 +--- a/drivers/thermal/qcom/tsens-v0_1.c ++++ b/drivers/thermal/qcom/tsens-v0_1.c +@@ -539,6 +539,7 @@ static int calibrate_9607(struct tsens_priv *priv) + static struct tsens_features tsens_v0_1_feat = { + .ver_major = VER_0_1, + .crit_int = 0, ++ .combo_int = 0, + .adc = 1, + .srot_split = 1, + .max_sensors = 11, +diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c +index 573e261ccca7..a4f561a6e582 100644 +--- a/drivers/thermal/qcom/tsens-v1.c ++++ b/drivers/thermal/qcom/tsens-v1.c +@@ -302,6 +302,7 @@ static int calibrate_8976(struct tsens_priv *priv) + static struct tsens_features tsens_v1_feat = { + .ver_major = VER_1_X, + .crit_int = 0, ++ .combo_int = 0, + .adc = 1, + .srot_split = 1, + .max_sensors = 11, +diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c +index b293ed32174b..129cdb247381 100644 +--- a/drivers/thermal/qcom/tsens-v2.c ++++ b/drivers/thermal/qcom/tsens-v2.c +@@ -31,6 +31,7 @@ + static struct tsens_features tsens_v2_feat = { + .ver_major = VER_2_X, + .crit_int = 1, ++ .combo_int = 0, + .adc = 0, + .srot_split = 1, + .max_sensors = 16, +diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c +index 99a8d9f3e03c..11172afbc5da 100644 +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -531,6 +531,27 @@ static irqreturn_t tsens_irq_thread(int irq, void *data) + return IRQ_HANDLED; + } + ++/** ++ * tsens_combined_irq_thread - Threaded interrupt handler for combined interrupts ++ * @irq: irq number ++ * @data: tsens controller private data ++ * ++ * Handle the combined interrupt as if it were 2 separate interrupts, so call the ++ * critical handler first and then the up/low one. ++ * ++ * Return: IRQ_HANDLED ++ */ ++static irqreturn_t tsens_combined_irq_thread(int irq, void *data) ++{ ++ irqreturn_t ret; ++ ++ ret = tsens_critical_irq_thread(irq, data); ++ if (ret != IRQ_HANDLED) ++ return ret; ++ ++ return tsens_irq_thread(irq, data); ++} ++ + static int tsens_set_trips(void *_sensor, int low, int high) + { + struct tsens_sensor *s = _sensor; +@@ -1075,13 +1096,18 @@ static int tsens_register(struct tsens_priv *priv) + tsens_mC_to_hw(priv->sensor, 0)); + } + +- ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); +- if (ret < 0) +- return ret; ++ if (priv->feat->combo_int) { ++ ret = tsens_register_irq(priv, "combined", ++ tsens_combined_irq_thread); ++ } else { ++ ret = tsens_register_irq(priv, "uplow", tsens_irq_thread); ++ if (ret < 0) ++ return ret; + +- if (priv->feat->crit_int) +- ret = tsens_register_irq(priv, "critical", +- tsens_critical_irq_thread); ++ if (priv->feat->crit_int) ++ ret = tsens_register_irq(priv, "critical", ++ tsens_critical_irq_thread); ++ } + + return ret; + } +diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h +index 1471a2c00f15..4614177944d6 100644 +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -495,6 +495,7 @@ enum regfield_ids { + * struct tsens_features - Features supported by the IP + * @ver_major: Major number of IP version + * @crit_int: does the IP support critical interrupts? ++ * @combo_int: does the IP use one IRQ for up, low and critical thresholds? + * @adc: do the sensors only output adc code (instead of temperature)? + * @srot_split: does the IP neatly splits the register space into SROT and TM, + * with SROT only being available to secure boot firmware? +@@ -504,6 +505,7 @@ enum regfield_ids { + struct tsens_features { + unsigned int ver_major; + unsigned int crit_int:1; ++ unsigned int combo_int:1; + unsigned int adc:1; + unsigned int srot_split:1; + unsigned int has_watchdog:1; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0119-drivers-thermal-tsens-allow-configuring-min-and-max-.patch b/target/linux/ipq60xx/patches-5.15/0119-drivers-thermal-tsens-allow-configuring-min-and-max-.patch new file mode 100644 index 000000000..602a713ca --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0119-drivers-thermal-tsens-allow-configuring-min-and-max-.patch @@ -0,0 +1,115 @@ +From f035a370b770831f1c4a5d5b5b4387391ee3f71a Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 28 Apr 2022 19:06:29 +0200 +Subject: [PATCH 119/137] drivers: thermal: tsens: allow configuring min and + max trips + +IPQ8074 and IPQ6018 dont support negative trip temperatures and support +up to 204 degrees C as the max trip temperature. + +So, instead of always setting the -40 as min and 120 degrees C as max +allow it to be configured as part of the features. + +Signed-off-by: Robert Marko +Reviewed-by: Bjorn Andersson +--- + drivers/thermal/qcom/tsens-8960.c | 2 ++ + drivers/thermal/qcom/tsens-v0_1.c | 2 ++ + drivers/thermal/qcom/tsens-v1.c | 2 ++ + drivers/thermal/qcom/tsens-v2.c | 2 ++ + drivers/thermal/qcom/tsens.c | 4 ++-- + drivers/thermal/qcom/tsens.h | 4 ++++ + 6 files changed, 14 insertions(+), 2 deletions(-) + +diff --git a/drivers/thermal/qcom/tsens-8960.c b/drivers/thermal/qcom/tsens-8960.c +index ee584e5b07e5..4585904fb380 100644 +--- a/drivers/thermal/qcom/tsens-8960.c ++++ b/drivers/thermal/qcom/tsens-8960.c +@@ -273,6 +273,8 @@ static struct tsens_features tsens_8960_feat = { + .adc = 1, + .srot_split = 0, + .max_sensors = 11, ++ .trip_min_temp = -40000, ++ .trip_max_temp = 120000, + }; + + struct tsens_plat_data data_8960 = { +diff --git a/drivers/thermal/qcom/tsens-v0_1.c b/drivers/thermal/qcom/tsens-v0_1.c +index 6effb822bf3c..2c203ff374e6 100644 +--- a/drivers/thermal/qcom/tsens-v0_1.c ++++ b/drivers/thermal/qcom/tsens-v0_1.c +@@ -543,6 +543,8 @@ static struct tsens_features tsens_v0_1_feat = { + .adc = 1, + .srot_split = 1, + .max_sensors = 11, ++ .trip_min_temp = -40000, ++ .trip_max_temp = 120000, + }; + + static const struct reg_field tsens_v0_1_regfields[MAX_REGFIELDS] = { +diff --git a/drivers/thermal/qcom/tsens-v1.c b/drivers/thermal/qcom/tsens-v1.c +index a4f561a6e582..1d7f8a80bd13 100644 +--- a/drivers/thermal/qcom/tsens-v1.c ++++ b/drivers/thermal/qcom/tsens-v1.c +@@ -306,6 +306,8 @@ static struct tsens_features tsens_v1_feat = { + .adc = 1, + .srot_split = 1, + .max_sensors = 11, ++ .trip_min_temp = -40000, ++ .trip_max_temp = 120000, + }; + + static const struct reg_field tsens_v1_regfields[MAX_REGFIELDS] = { +diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c +index 129cdb247381..9babc69bfd22 100644 +--- a/drivers/thermal/qcom/tsens-v2.c ++++ b/drivers/thermal/qcom/tsens-v2.c +@@ -35,6 +35,8 @@ static struct tsens_features tsens_v2_feat = { + .adc = 0, + .srot_split = 1, + .max_sensors = 16, ++ .trip_min_temp = -40000, ++ .trip_max_temp = 120000, + }; + + static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = { +diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c +index 11172afbc5da..e11861581970 100644 +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -572,8 +572,8 @@ static int tsens_set_trips(void *_sensor, int low, int high) + dev_dbg(dev, "[%u] %s: proposed thresholds: (%d:%d)\n", + hw_id, __func__, low, high); + +- cl_high = clamp_val(high, -40000, 120000); +- cl_low = clamp_val(low, -40000, 120000); ++ cl_high = clamp_val(high, priv->feat->trip_min_temp, priv->feat->trip_max_temp); ++ cl_low = clamp_val(low, priv->feat->trip_min_temp, priv->feat->trip_max_temp); + + high_val = tsens_mC_to_hw(s, cl_high); + low_val = tsens_mC_to_hw(s, cl_low); +diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h +index 4614177944d6..747004476347 100644 +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -501,6 +501,8 @@ enum regfield_ids { + * with SROT only being available to secure boot firmware? + * @has_watchdog: does this IP support watchdog functionality? + * @max_sensors: maximum sensors supported by this version of the IP ++ * @trip_min_temp: minimum trip temperature supported by this version of the IP ++ * @trip_max_temp: maximum trip temperature supported by this version of the IP + */ + struct tsens_features { + unsigned int ver_major; +@@ -510,6 +512,8 @@ struct tsens_features { + unsigned int srot_split:1; + unsigned int has_watchdog:1; + unsigned int max_sensors; ++ int trip_min_temp; ++ int trip_max_temp; + }; + + /** +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0120-drivers-thermal-tsens-add-IPQ8074-support.patch b/target/linux/ipq60xx/patches-5.15/0120-drivers-thermal-tsens-add-IPQ8074-support.patch new file mode 100644 index 000000000..2ecf2e00e --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0120-drivers-thermal-tsens-add-IPQ8074-support.patch @@ -0,0 +1,81 @@ +From ffc91d0fc802e58f44c7f888f44643847b6dfa3a Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 28 Apr 2022 20:26:13 +0200 +Subject: [PATCH 120/137] drivers: thermal: tsens: add IPQ8074 support + +Qualcomm IPQ8074 uses tsens v2.3 IP, however unlike other tsens v2 IP +it only has one IRQ, that is used for up/low as well as critical. +It also does not support negative trip temperatures. + +Signed-off-by: Robert Marko +Reviewed-by: Bjorn Andersson +--- + drivers/thermal/qcom/tsens-v2.c | 17 +++++++++++++++++ + drivers/thermal/qcom/tsens.c | 3 +++ + drivers/thermal/qcom/tsens.h | 2 +- + 3 files changed, 21 insertions(+), 1 deletion(-) + +diff --git a/drivers/thermal/qcom/tsens-v2.c b/drivers/thermal/qcom/tsens-v2.c +index 9babc69bfd22..29a61d2d6ca3 100644 +--- a/drivers/thermal/qcom/tsens-v2.c ++++ b/drivers/thermal/qcom/tsens-v2.c +@@ -39,6 +39,17 @@ static struct tsens_features tsens_v2_feat = { + .trip_max_temp = 120000, + }; + ++static struct tsens_features ipq8074_feat = { ++ .ver_major = VER_2_X, ++ .crit_int = 1, ++ .combo_int = 1, ++ .adc = 0, ++ .srot_split = 1, ++ .max_sensors = 16, ++ .trip_min_temp = 0, ++ .trip_max_temp = 204000, ++}; ++ + static const struct reg_field tsens_v2_regfields[MAX_REGFIELDS] = { + /* ----- SROT ------ */ + /* VERSION */ +@@ -104,6 +115,12 @@ struct tsens_plat_data data_tsens_v2 = { + .fields = tsens_v2_regfields, + }; + ++struct tsens_plat_data data_ipq8074 = { ++ .ops = &ops_generic_v2, ++ .feat = &ipq8074_feat, ++ .fields = tsens_v2_regfields, ++}; ++ + /* Kept around for backward compatibility with old msm8996.dtsi */ + struct tsens_plat_data data_8996 = { + .num_sensors = 13, +diff --git a/drivers/thermal/qcom/tsens.c b/drivers/thermal/qcom/tsens.c +index e11861581970..60b65bd2e7a3 100644 +--- a/drivers/thermal/qcom/tsens.c ++++ b/drivers/thermal/qcom/tsens.c +@@ -990,6 +990,9 @@ static const struct of_device_id tsens_table[] = { + { + .compatible = "qcom,ipq8064-tsens", + .data = &data_8960, ++ }, { ++ .compatible = "qcom,ipq8074-tsens", ++ .data = &data_ipq8074, + }, { + .compatible = "qcom,mdm9607-tsens", + .data = &data_9607, +diff --git a/drivers/thermal/qcom/tsens.h b/drivers/thermal/qcom/tsens.h +index 747004476347..8dd990d944ad 100644 +--- a/drivers/thermal/qcom/tsens.h ++++ b/drivers/thermal/qcom/tsens.h +@@ -599,6 +599,6 @@ extern struct tsens_plat_data data_8916, data_8939, data_8974, data_9607; + 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_ipq8074, data_tsens_v2; + + #endif /* __QCOM_TSENS_H__ */ +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0121-arm64-dts-ipq8074-add-thermal-nodes.patch b/target/linux/ipq60xx/patches-5.15/0121-arm64-dts-ipq8074-add-thermal-nodes.patch new file mode 100644 index 000000000..10d90cd79 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0121-arm64-dts-ipq8074-add-thermal-nodes.patch @@ -0,0 +1,136 @@ +From 1cdc1eaed3ea5b2cab82dee4c72c3cea23356ca6 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 24 Dec 2021 20:33:59 +0100 +Subject: [PATCH 121/137] 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 +--- +Changes in v5: +* Rebase to apply on next-20220708 +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 96 +++++++++++++++++++++++++++ + 1 file changed, 96 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 019c8e302273..9707136f08e1 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -310,6 +310,16 @@ prng: rng@e3000 { + status = "disabled"; + }; + ++ tsens: thermal-sensor@4a9000 { ++ compatible = "qcom,ipq8074-tsens"; ++ reg = <0x4a9000 0x1000>, /* TM */ ++ <0x4a8000 0x1000>; /* SROT */ ++ interrupts = ; ++ interrupt-names = "combined"; ++ #qcom,sensors = <16>; ++ #thermal-sensor-cells = <1>; ++ }; ++ + cryptobam: dma-controller@704000 { + compatible = "qcom,bam-v1.7.0"; + reg = <0x00704000 0x20000>; +@@ -920,4 +930,90 @@ timer { + , + ; + }; ++ ++ 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>; ++ }; ++ }; + }; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0122-mfd-qcom-spmi-pmic-add-support-for-PMP8074.patch b/target/linux/ipq60xx/patches-5.15/0122-mfd-qcom-spmi-pmic-add-support-for-PMP8074.patch new file mode 100644 index 000000000..f2e64bd97 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0122-mfd-qcom-spmi-pmic-add-support-for-PMP8074.patch @@ -0,0 +1,30 @@ +From 0d176e7d075fb7deeb1b137fec56304a402f2f25 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 19 May 2022 14:51:53 +0200 +Subject: [PATCH 122/137] mfd: qcom-spmi-pmic: add support for PMP8074 + +Add support for PMP8074 PMIC which is a companion PMIC for the Qualcomm +IPQ8074 SoC-s. + +It shares the same subtype identifier as PM8901. + +Signed-off-by: Robert Marko +--- + drivers/mfd/qcom-spmi-pmic.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/mfd/qcom-spmi-pmic.c b/drivers/mfd/qcom-spmi-pmic.c +index 1cacc00aa6c9..664de43df690 100644 +--- a/drivers/mfd/qcom-spmi-pmic.c ++++ b/drivers/mfd/qcom-spmi-pmic.c +@@ -76,6 +76,7 @@ static const struct of_device_id pmic_spmi_id_table[] = { + { .compatible = "qcom,pmi8994", .data = (void *)PMI8994_SUBTYPE }, + { .compatible = "qcom,pmi8998", .data = (void *)PMI8998_SUBTYPE }, + { .compatible = "qcom,pmk8002", .data = (void *)PMK8002_SUBTYPE }, ++ { .compatible = "qcom,pmp8074", .data = (void *)PM8901_SUBTYPE }, + { .compatible = "qcom,smb2351", .data = (void *)SMB2351_SUBTYPE }, + { .compatible = "qcom,spmi-pmic", .data = (void *)COMMON_SUBTYPE }, + { } +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0123-arm64-dts-qcom-add-PMP8074-DTSI.patch b/target/linux/ipq60xx/patches-5.15/0123-arm64-dts-qcom-add-PMP8074-DTSI.patch new file mode 100644 index 000000000..57f911248 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0123-arm64-dts-qcom-add-PMP8074-DTSI.patch @@ -0,0 +1,164 @@ +From 2abf33c722b4ade9438dc91a652f294ada68a50e Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Wed, 18 May 2022 16:36:42 +0200 +Subject: [PATCH 123/137] arm64: dts: qcom: add PMP8074 DTSI + +PMP8074 is a companion PMIC to the Qualcomm IPQ8074 series that is +controlled via SPMI. + +Add DTSI for it providing GPIO, regulator, RTC and VADC support. + +RTC is disabled by default as there is no built-in battery so it will +loose time unless board vendor added a battery, so make it optional. + +Signed-off-by: Robert Marko +--- +Changes in v7: +* Dual license with BSD-3-Clause +* Use "-" instead of underscores in node names + +Changes in v6: +* Add RTC and GPIO nodes + +Changes in v5: +* Remove #address-cells and #size-cells as they are not required for +regulator subnodes +--- + arch/arm64/boot/dts/qcom/pmp8074.dtsi | 125 ++++++++++++++++++++++++++ + 1 file changed, 125 insertions(+) + create mode 100644 arch/arm64/boot/dts/qcom/pmp8074.dtsi + +diff --git a/arch/arm64/boot/dts/qcom/pmp8074.dtsi b/arch/arm64/boot/dts/qcom/pmp8074.dtsi +new file mode 100644 +index 000000000000..ceb2e6358b3d +--- /dev/null ++++ b/arch/arm64/boot/dts/qcom/pmp8074.dtsi +@@ -0,0 +1,125 @@ ++// SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause ++ ++#include ++#include ++ ++&spmi_bus { ++ pmic@0 { ++ compatible = "qcom,pmp8074", "qcom,spmi-pmic"; ++ reg = <0x0 SPMI_USID>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ pmp8074_adc: adc@3100 { ++ compatible = "qcom,spmi-adc-rev2"; ++ reg = <0x3100>; ++ interrupts = <0x0 0x31 0x0 IRQ_TYPE_EDGE_RISING>; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ #io-channel-cells = <1>; ++ ++ ref-gnd@0 { ++ reg = ; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ vref-1p25@1 { ++ reg = ; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ vref-vadc@2 { ++ reg = ; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ pmic_die: die-temp@6 { ++ reg = ; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ xo_therm: xo-temp@76 { ++ reg = ; ++ qcom,ratiometric; ++ qcom,hw-settle-time = <200>; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ pa_therm1: thermistor1@77 { ++ reg = ; ++ qcom,ratiometric; ++ qcom,hw-settle-time = <200>; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ pa_therm2: thermistor2@78 { ++ reg = ; ++ qcom,ratiometric; ++ qcom,hw-settle-time = <200>; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ pa_therm3: thermistor3@79 { ++ reg = ; ++ qcom,ratiometric; ++ qcom,hw-settle-time = <200>; ++ qcom,pre-scaling = <1 1>; ++ }; ++ ++ vph-pwr@131 { ++ reg = ; ++ qcom,pre-scaling = <1 3>; ++ }; ++ }; ++ ++ pmp8074_rtc: rtc@6000 { ++ compatible = "qcom,pm8941-rtc"; ++ reg = <0x6000>; ++ reg-names = "rtc", "alarm"; ++ interrupts = <0x0 0x61 0x1 IRQ_TYPE_NONE>; ++ allow-set-time; ++ status = "disabled"; ++ }; ++ ++ pmp8074_gpios: gpio@c000 { ++ compatible = "qcom,pmp8074-gpio", "qcom,spmi-gpio"; ++ reg = <0xc000>; ++ gpio-controller; ++ #gpio-cells = <2>; ++ gpio-ranges = <&pmp8074_gpios 0 0 12>; ++ interrupt-controller; ++ #interrupt-cells = <2>; ++ }; ++ }; ++ ++ pmic@1 { ++ compatible = "qcom,pmp8074", "qcom,spmi-pmic"; ++ reg = <0x1 SPMI_USID>; ++ ++ regulators { ++ compatible = "qcom,pmp8074-regulators"; ++ ++ s3: s3 { ++ regulator-name = "vdd_s3"; ++ regulator-min-microvolt = <592000>; ++ regulator-max-microvolt = <1064000>; ++ regulator-always-on; ++ regulator-boot-on; ++ }; ++ ++ s4: s4 { ++ regulator-name = "vdd_s4"; ++ regulator-min-microvolt = <712000>; ++ regulator-max-microvolt = <992000>; ++ regulator-always-on; ++ regulator-boot-on; ++ }; ++ ++ l11: l11 { ++ regulator-name = "l11"; ++ regulator-min-microvolt = <1800000>; ++ regulator-max-microvolt = <3300000>; ++ }; ++ }; ++ }; ++}; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0124-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch b/target/linux/ipq60xx/patches-5.15/0124-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch new file mode 100644 index 000000000..0a52509a8 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0124-arm64-dts-qcom-ipq8074-hk01-add-VQMMC-supply.patch @@ -0,0 +1,40 @@ +From 89f8d101005ee40b3a6c689ba1765403d3617672 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Thu, 19 May 2022 13:34:03 +0200 +Subject: [PATCH 124/137] arm64: dts: qcom: ipq8074-hk01: add VQMMC supply + +Since now we have control over the PMP8074 PMIC providing various system +voltages including L11 which provides the SDIO/eMMC I/O voltage set it as +the SDHCI VQMMC supply. + +This allows SDHCI controller to switch to 1.8V I/O mode and support high +speed modes like HS200 and HS400. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074-hk01.dts | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts +index ef30053136e3..f2b2691cc463 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts ++++ b/arch/arm64/boot/dts/qcom/ipq8074-hk01.dts +@@ -3,6 +3,7 @@ + /* Copyright (c) 2017, The Linux Foundation. All rights reserved. + */ + #include "ipq8074.dtsi" ++#include "pmp8074.dtsi" + + / { + model = "Qualcomm Technologies, Inc. IPQ8074-HK01"; +@@ -82,6 +83,7 @@ nand@0 { + + &sdhc_1 { + status = "okay"; ++ vqmmc-supply = <&l11>; + }; + + &qusb_phy_0 { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0125-arm64-dts-ipq8074-add-CPU-clock.patch b/target/linux/ipq60xx/patches-5.15/0125-arm64-dts-ipq8074-add-CPU-clock.patch new file mode 100644 index 000000000..c017de2f8 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0125-arm64-dts-ipq8074-add-CPU-clock.patch @@ -0,0 +1,64 @@ +From 7b7941649605363d0eebc9fdfb84a13a95522cfb Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 31 Dec 2021 17:56:14 +0100 +Subject: [PATCH 125/137] arm64: dts: ipq8074: add CPU clock + +Now that CPU clock is exposed and can be controlled, add the necessary +properties to the CPU nodes. + +Signed-off-by: Robert Marko +--- + arch/arm64/boot/dts/qcom/ipq8074.dtsi | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 9707136f08e1..b9030cb8fbf3 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -5,6 +5,7 @@ + + #include + #include ++#include + + / { + #address-cells = <2>; +@@ -38,6 +39,8 @@ CPU0: cpu@0 { + reg = <0x0>; + next-level-cache = <&L2_0>; + enable-method = "psci"; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; + }; + + CPU1: cpu@1 { +@@ -46,6 +49,8 @@ CPU1: cpu@1 { + enable-method = "psci"; + reg = <0x1>; + next-level-cache = <&L2_0>; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; + }; + + CPU2: cpu@2 { +@@ -54,6 +59,8 @@ CPU2: cpu@2 { + enable-method = "psci"; + reg = <0x2>; + next-level-cache = <&L2_0>; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; + }; + + CPU3: cpu@3 { +@@ -62,6 +69,8 @@ CPU3: cpu@3 { + enable-method = "psci"; + reg = <0x3>; + next-level-cache = <&L2_0>; ++ clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; ++ clock-names = "cpu"; + }; + + L2_0: l2-cache { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0126-arm64-dts-ipq8074-add-label-to-cpus.patch b/target/linux/ipq60xx/patches-5.15/0126-arm64-dts-ipq8074-add-label-to-cpus.patch new file mode 100644 index 000000000..684366f88 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0126-arm64-dts-ipq8074-add-label-to-cpus.patch @@ -0,0 +1,30 @@ +From ff74c990c4b671f17d0dfc2c93bae9e23b017472 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 31 Dec 2021 18:42:53 +0100 +Subject: [PATCH 126/137] 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(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index b9030cb8fbf3..bcf2163f527f 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -29,7 +29,7 @@ xo: xo { + }; + }; + +- cpus { ++ cpus: cpus { + #address-cells = <0x1>; + #size-cells = <0x0>; + +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0127-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch b/target/linux/ipq60xx/patches-5.15/0127-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch new file mode 100644 index 000000000..08a56a6f1 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0127-arm64-dts-ipq8074-add-cooling-cells-to-CPU-nodes.patch @@ -0,0 +1,53 @@ +From 8ddd2743d7bd30165b0c5e1abb6990da15c181d4 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Fri, 31 Dec 2021 20:38:06 +0100 +Subject: [PATCH 127/137] 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(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index bcf2163f527f..8094bef3f28f 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -41,6 +41,7 @@ CPU0: cpu@0 { + enable-method = "psci"; + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; ++ #cooling-cells = <2>; + }; + + CPU1: cpu@1 { +@@ -51,6 +52,7 @@ CPU1: cpu@1 { + next-level-cache = <&L2_0>; + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; ++ #cooling-cells = <2>; + }; + + CPU2: cpu@2 { +@@ -61,6 +63,7 @@ CPU2: cpu@2 { + next-level-cache = <&L2_0>; + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; ++ #cooling-cells = <2>; + }; + + CPU3: cpu@3 { +@@ -71,6 +74,7 @@ CPU3: cpu@3 { + next-level-cache = <&L2_0>; + clocks = <&apcs_glb APCS_ALIAS0_CORE_CLK>; + clock-names = "cpu"; ++ #cooling-cells = <2>; + }; + + L2_0: l2-cache { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0128-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch b/target/linux/ipq60xx/patches-5.15/0128-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch new file mode 100644 index 000000000..243f8f37e --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0128-dt-bindings-clock-qcom-Add-reset-for-WCSSAON.patch @@ -0,0 +1,31 @@ +From aa0c4a764d290cceba0a27fd5d81b30b54c5c81f Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:10 +0530 +Subject: [PATCH 128/137] 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(+) + +diff --git a/include/dt-bindings/clock/qcom,gcc-ipq8074.h b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +index e4991d303708..7b425cede707 100644 +--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +@@ -367,6 +367,7 @@ + #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 + + #define USB0_GDSC 0 + #define USB1_GDSC 1 +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0129-clk-qcom-Add-WCSSAON-reset.patch b/target/linux/ipq60xx/patches-5.15/0129-clk-qcom-Add-WCSSAON-reset.patch new file mode 100644 index 000000000..3493a8ceb --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0129-clk-qcom-Add-WCSSAON-reset.patch @@ -0,0 +1,30 @@ +From 1377fc72a67c8684237fa9b1f246257fd073b2b1 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:11 +0530 +Subject: [PATCH 129/137] 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(+) + +diff --git a/drivers/clk/qcom/gcc-ipq8074.c b/drivers/clk/qcom/gcc-ipq8074.c +index 42d185fe19c8..7954a730aad2 100644 +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4826,6 +4826,7 @@ static const struct qcom_reset_map gcc_ipq8074_resets[] = { + [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 struct gdsc *gcc_ipq8074_gdscs[] = { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0130-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch b/target/linux/ipq60xx/patches-5.15/0130-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch new file mode 100644 index 000000000..38cfecfcf --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0130-remoteproc-wcss-disable-auto-boot-for-IPQ8074.patch @@ -0,0 +1,52 @@ +From d9965ec6f02f71609d837d33d4bae20fb7dec1fd Mon Sep 17 00:00:00 2001 +From: Sivaprakash Murugesan +Date: Fri, 17 Apr 2020 16:37:10 +0530 +Subject: [PATCH 130/137] 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(+) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 16fc5a33adaf..92c240976f55 100644 +--- 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 platform_device *pdev) + 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_ipq8074_res_init = { + .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_qcs404_res_init = { + .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[] = { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0131-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch b/target/linux/ipq60xx/patches-5.15/0131-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch new file mode 100644 index 000000000..586c33158 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0131-arm64-dts-qcom-Enable-Q6v5-WCSS-for-ipq8074-SoC.patch @@ -0,0 +1,125 @@ +From 45dfcd1ecf0910e4e45fec5f26f2fc80a47732f9 Mon Sep 17 00:00:00 2001 +From: Gokul Sriram Palanisamy +Date: Sat, 30 Jan 2021 10:50:13 +0530 +Subject: [PATCH 131/137] 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 | 81 +++++++++++++++++++++++++++ + 1 file changed, 81 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 8094bef3f28f..6c18e2f91f49 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -153,6 +153,32 @@ scm { + }; + }; + ++ 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>; +@@ -421,6 +447,11 @@ tcsr_mutex: hwlock@1905000 { + #hwlock-cells = <1>; + }; + ++ tcsr_q6: syscon@1945000 { ++ compatible = "syscon"; ++ reg = <0x01945000 0xe000>; ++ }; ++ + spmi_bus: spmi@200f000 { + compatible = "qcom,spmi-pmic-arb"; + reg = <0x0200f000 0x001000>, +@@ -934,6 +965,56 @@ IRQ_TYPE_LEVEL_HIGH>, /* int_c */ + "axi_s_sticky"; + status = "disabled"; + }; ++ ++ q6v5_wcss: q6v5_wcss@cd00000 { ++ compatible = "qcom,ipq8074-wcss-pil"; ++ reg = <0x0cd00000 0x4040>, ++ <0x004ab000 0x20>; ++ reg-names = "qdsp6", ++ "rmb"; ++ qca,auto-restart; ++ qca,extended-intc; ++ interrupts-extended = <&intc 0 325 1>, ++ <&wcss_smp2p_in 0 0>, ++ <&wcss_smp2p_in 1 0>, ++ <&wcss_smp2p_in 2 0>, ++ <&wcss_smp2p_in 3 0>; ++ interrupt-names = "wdog", ++ "fatal", ++ "ready", ++ "handover", ++ "stop-ack"; ++ ++ resets = <&gcc GCC_WCSSAON_RESET>, ++ <&gcc GCC_WCSS_BCR>, ++ <&gcc GCC_WCSS_Q6_BCR>; ++ ++ reset-names = "wcss_aon_reset", ++ "wcss_reset", ++ "wcss_q6_reset"; ++ ++ clocks = <&gcc GCC_PRNG_AHB_CLK>; ++ clock-names = "prng"; ++ ++ qcom,halt-regs = <&tcsr_q6 0xa000 0xd000 0x0>; ++ ++ qcom,smem-states = <&wcss_smp2p_out 0>, ++ <&wcss_smp2p_out 1>; ++ qcom,smem-state-names = "shutdown", ++ "stop"; ++ ++ memory-region = <&q6_region>; ++ ++ glink-edge { ++ interrupts = ; ++ qcom,remote-pid = <1>; ++ mboxes = <&apcs_glb 8>; ++ ++ rpm_requests { ++ qcom,glink-channels = "IPCRTR"; ++ }; ++ }; ++ }; + }; + + timer { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0132-arm64-dts-ipq8074-Add-WLAN-node.patch b/target/linux/ipq60xx/patches-5.15/0132-arm64-dts-ipq8074-Add-WLAN-node.patch new file mode 100644 index 000000000..ac494377d --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0132-arm64-dts-ipq8074-Add-WLAN-node.patch @@ -0,0 +1,140 @@ +From d8e8adbf99ed4a3b50f87a665661a8cad84918ff Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 21 Dec 2021 14:49:36 +0100 +Subject: [PATCH 132/137] 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(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 6c18e2f91f49..6b743a0a740f 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -1015,6 +1015,117 @@ rpm_requests { + }; + }; + }; ++ ++ 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"; ++ }; + }; + + timer { +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0133-clk-ipq-support-for-resetting-multiple-bits.patch b/target/linux/ipq60xx/patches-5.15/0133-clk-ipq-support-for-resetting-multiple-bits.patch new file mode 100644 index 000000000..fd1bd4fc6 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0133-clk-ipq-support-for-resetting-multiple-bits.patch @@ -0,0 +1,63 @@ +From e362372fa8d56c669516dc83abe75cd057d94171 Mon Sep 17 00:00:00 2001 +From: Rajkumar Ayyasamy +Date: Wed, 18 Mar 2020 17:08:11 +0530 +Subject: [PATCH 133/137] 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(-) + +diff --git a/drivers/clk/qcom/reset.c b/drivers/clk/qcom/reset.c +index 819d194be8f7..8ad7b50dd534 100644 +--- a/drivers/clk/qcom/reset.c ++++ b/drivers/clk/qcom/reset.c +@@ -28,7 +28,7 @@ qcom_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) + + 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_controller_dev *rcdev, unsigned long id) + + 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); + } +diff --git a/drivers/clk/qcom/reset.h b/drivers/clk/qcom/reset.h +index 2a08b5e282c7..0410f83bf2bb 100644 +--- 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; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0134-clk-qcom-ipq8074-add-missing-networking-resets.patch b/target/linux/ipq60xx/patches-5.15/0134-clk-qcom-ipq8074-add-missing-networking-resets.patch new file mode 100644 index 000000000..3e140bcc1 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0134-clk-qcom-ipq8074-add-missing-networking-resets.patch @@ -0,0 +1,70 @@ +From ad08de7cb6308521b4a80c427a7cbec84742a729 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Sat, 1 Jan 2022 18:15:03 +0100 +Subject: [PATCH 134/137] 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(+) + +diff --git a/drivers/clk/qcom/gcc-ipq8074.c b/drivers/clk/qcom/gcc-ipq8074.c +index 7954a730aad2..069ad3b5bca0 100644 +--- a/drivers/clk/qcom/gcc-ipq8074.c ++++ b/drivers/clk/qcom/gcc-ipq8074.c +@@ -4827,6 +4827,20 @@ static const struct qcom_reset_map gcc_ipq8074_resets[] = { + [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 struct gdsc *gcc_ipq8074_gdscs[] = { +diff --git a/include/dt-bindings/clock/qcom,gcc-ipq8074.h b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +index 7b425cede707..1e9ef05a36dd 100644 +--- a/include/dt-bindings/clock/qcom,gcc-ipq8074.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq8074.h +@@ -368,6 +368,20 @@ + #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 + + #define USB0_GDSC 0 + #define USB1_GDSC 1 +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0135-arm64-dts-ipq8074-add-label-to-clocks.patch b/target/linux/ipq60xx/patches-5.15/0135-arm64-dts-ipq8074-add-label-to-clocks.patch new file mode 100644 index 000000000..b39a580b2 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0135-arm64-dts-ipq8074-add-label-to-clocks.patch @@ -0,0 +1,29 @@ +From c7b874696964bab2de6b08a44168c42a556a077c Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Wed, 9 Feb 2022 23:13:26 +0100 +Subject: [PATCH 135/137] 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(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq8074.dtsi b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +index 6b743a0a740f..f30bf5ef6aab 100644 +--- a/arch/arm64/boot/dts/qcom/ipq8074.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq8074.dtsi +@@ -15,7 +15,7 @@ / { + compatible = "qcom,ipq8074"; + interrupt-parent = <&intc>; + +- clocks { ++ clocks: clocks { + sleep_clk: sleep_clk { + compatible = "fixed-clock"; + clock-frequency = <32768>; +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0136-power-Add-Qualcomm-APM.patch b/target/linux/ipq60xx/patches-5.15/0136-power-Add-Qualcomm-APM.patch new file mode 100644 index 000000000..2c1ab0dd7 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0136-power-Add-Qualcomm-APM.patch @@ -0,0 +1,1066 @@ +From 715f1016a6083b2a7509cb08a4f5ea0bc9661020 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 11 Apr 2022 14:38:08 +0200 +Subject: [PATCH 136/137] power: Add Qualcomm APM + +Allow building Qualcomm APM. + +Signed-off-by: Robert Marko +--- + drivers/power/Kconfig | 1 + + drivers/power/Makefile | 1 + + drivers/power/qcom/Kconfig | 7 + + drivers/power/qcom/Makefile | 1 + + drivers/power/qcom/apm.c | 944 +++++++++++++++++++++++++++++++++ + include/linux/power/qcom/apm.h | 48 ++ + 6 files changed, 1002 insertions(+) + create mode 100644 drivers/power/qcom/Kconfig + create mode 100644 drivers/power/qcom/Makefile + create mode 100644 drivers/power/qcom/apm.c + create mode 100644 include/linux/power/qcom/apm.h + +diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig +index 696bf77a7042..235f3cbf2a54 100644 +--- 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" +diff --git a/drivers/power/Makefile b/drivers/power/Makefile +index effbf0377f32..cd239e730b2b 100644 +--- 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/drivers/power/qcom/Kconfig b/drivers/power/qcom/Kconfig +new file mode 100644 +index 000000000000..01993a6179d1 +--- /dev/null ++++ b/drivers/power/qcom/Kconfig +@@ -0,0 +1,7 @@ ++config QCOM_APM ++ bool "Qualcomm Technologies Inc platform specific APM driver" ++ help ++ Platform specific driver to manage the power source of ++ memory arrays. Interfaces with regulator drivers to ensure ++ SRAM Vmin requirements are met across different performance ++ levels. +diff --git a/drivers/power/qcom/Makefile b/drivers/power/qcom/Makefile +new file mode 100644 +index 000000000000..43a288e62ef1 +--- /dev/null ++++ b/drivers/power/qcom/Makefile +@@ -0,0 +1 @@ ++obj-$(CONFIG_QCOM_APM) += apm.o +diff --git a/drivers/power/qcom/apm.c b/drivers/power/qcom/apm.c +new file mode 100644 +index 000000000000..bd93358e3d34 +--- /dev/null ++++ b/drivers/power/qcom/apm.c +@@ -0,0 +1,944 @@ ++/* ++ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#define pr_fmt(fmt) "%s: " fmt, __func__ ++ ++#include ++#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/include/linux/power/qcom/apm.h b/include/linux/power/qcom/apm.h +new file mode 100644 +index 000000000000..432683578b76 +--- /dev/null ++++ b/include/linux/power/qcom/apm.h +@@ -0,0 +1,48 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __LINUX_POWER_QCOM_APM_H__ ++#define __LINUX_POWER_QCOM_APM_H__ ++ ++#include ++#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 +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/0137-regulator-add-Qualcomm-CPR-regulators.patch b/target/linux/ipq60xx/patches-5.15/0137-regulator-add-Qualcomm-CPR-regulators.patch new file mode 100644 index 000000000..6f22eeb28 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/0137-regulator-add-Qualcomm-CPR-regulators.patch @@ -0,0 +1,12171 @@ +From 15db7a6341e0d01c12cd9a76c1d9f51a6bca56cf Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Mon, 11 Apr 2022 14:35:36 +0200 +Subject: [PATCH 137/137] regulator: add Qualcomm CPR regulators + +Allow building Qualcomm CPR regulators. + +Signed-off-by: Robert Marko +--- + drivers/regulator/Kconfig | 33 + + drivers/regulator/Makefile | 3 + + drivers/regulator/cpr3-npu-regulator.c | 695 +++ + drivers/regulator/cpr3-regulator.c | 5112 +++++++++++++++++++++++ + drivers/regulator/cpr3-regulator.h | 1211 ++++++ + drivers/regulator/cpr3-util.c | 2750 ++++++++++++ + drivers/regulator/cpr4-apss-regulator.c | 1819 ++++++++ + include/soc/qcom/socinfo.h | 463 ++ + 8 files changed, 12086 insertions(+) + create mode 100644 drivers/regulator/cpr3-npu-regulator.c + create mode 100644 drivers/regulator/cpr3-regulator.c + create mode 100644 drivers/regulator/cpr3-regulator.h + create mode 100644 drivers/regulator/cpr3-util.c + create mode 100644 drivers/regulator/cpr4-apss-regulator.c + create mode 100644 include/soc/qcom/socinfo.h + +diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig +index 4fd13b06231f..92164c80cbc4 100644 +--- 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 + +diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile +index 9e382b50a5ef..2e521cb39e7e 100644 +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -105,6 +105,9 @@ obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o + 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/drivers/regulator/cpr3-npu-regulator.c b/drivers/regulator/cpr3-npu-regulator.c +new file mode 100644 +index 000000000000..e808073c0850 +--- /dev/null ++++ b/drivers/regulator/cpr3-npu-regulator.c +@@ -0,0 +1,695 @@ ++/* ++ * Copyright (c) 2017, The Linux Foundation. All rights reserved. ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include ++#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/drivers/regulator/cpr3-regulator.c b/drivers/regulator/cpr3-regulator.c +new file mode 100644 +index 000000000000..d5d8a1a0b061 +--- /dev/null ++++ b/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/drivers/regulator/cpr3-regulator.h b/drivers/regulator/cpr3-regulator.h +new file mode 100644 +index 000000000000..7c69c4630543 +--- /dev/null ++++ b/drivers/regulator/cpr3-regulator.h +@@ -0,0 +1,1211 @@ ++/* ++ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __REGULATOR_CPR3_REGULATOR_H__ ++#define __REGULATOR_CPR3_REGULATOR_H__ ++ ++#include ++#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/drivers/regulator/cpr3-util.c b/drivers/regulator/cpr3-util.c +new file mode 100644 +index 000000000000..45493af714e2 +--- /dev/null ++++ b/drivers/regulator/cpr3-util.c +@@ -0,0 +1,2750 @@ ++/* ++ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++/* ++ * This file contains utility functions to be used by platform specific CPR3 ++ * regulator drivers. ++ */ ++ ++#define pr_fmt(fmt) "%s: " fmt, __func__ ++ ++#include ++#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/drivers/regulator/cpr4-apss-regulator.c b/drivers/regulator/cpr4-apss-regulator.c +new file mode 100644 +index 000000000000..114e2acb25ff +--- /dev/null ++++ b/drivers/regulator/cpr4-apss-regulator.c +@@ -0,0 +1,1819 @@ ++/* ++ * Copyright (c) 2015-2016, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#define pr_fmt(fmt) "%s: " fmt, __func__ ++ ++#include ++#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/include/soc/qcom/socinfo.h b/include/soc/qcom/socinfo.h +new file mode 100644 +index 000000000000..db43446163a9 +--- /dev/null ++++ b/include/soc/qcom/socinfo.h +@@ -0,0 +1,463 @@ ++/* Copyright (c) 2009-2014, 2016, 2020, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#ifndef _ARCH_ARM_MACH_MSM_SOCINFO_H_ ++#define _ARCH_ARM_MACH_MSM_SOCINFO_H_ ++ ++#include ++ ++#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_ */ +-- +2.37.2 + diff --git a/target/linux/ipq60xx/patches-5.15/1000-arm64-dts-add-OpenWrt-DTS-files.patch b/target/linux/ipq60xx/patches-5.15/1000-arm64-dts-add-OpenWrt-DTS-files.patch new file mode 100644 index 000000000..91f4a0e22 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1000-arm64-dts-add-OpenWrt-DTS-files.patch @@ -0,0 +1,30 @@ +From 0b8ea8ba796dfeb9e9012ae07633e9df15c4c7da Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 11 May 2021 13:29:33 +0200 +Subject: [PATCH 1000/1011] arm64: dts: add OpenWrt DTS files + +Lets add custom OpenWrt DTS files to Makefile. + +Signed-off-by: Robert Marko +[mrnuke: Replace ipq807x dtbs with ipx601x] +Signed-off-by: Alexandru Gagniuc +--- + arch/arm64/boot/dts/qcom/Makefile | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile +index 70516508be56..27a7e3f98465 100644 +--- a/arch/arm64/boot/dts/qcom/Makefile ++++ b/arch/arm64/boot/dts/qcom/Makefile +@@ -4,6 +4,7 @@ dtb-$(CONFIG_ARCH_QCOM) += apq8094-sony-xperia-kitakami-karin_windy.dtb + 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) += ipq6018-qihoo-v6.dtb ++dtb-$(CONFIG_ARCH_QCOM) += ipq6018-eap610-outdoor.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk01.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk10-c1.dtb + dtb-$(CONFIG_ARCH_QCOM) += ipq8074-hk10-c2.dtb +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1001-arm64-dts-qcom-ipq6018-Add-mdio-bus-description.patch b/target/linux/ipq60xx/patches-5.15/1001-arm64-dts-qcom-ipq6018-Add-mdio-bus-description.patch new file mode 100644 index 000000000..f72ef1249 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1001-arm64-dts-qcom-ipq6018-Add-mdio-bus-description.patch @@ -0,0 +1,40 @@ +From e6beff2e1210057ff2226d3dae088e91ba2941dc Mon Sep 17 00:00:00 2001 +From: Baruch Siach +Date: Mon, 28 Feb 2022 08:58:44 +0200 +Subject: [PATCH 1001/1011] arm64: dts: qcom: ipq6018: Add mdio bus description + +The IPQ60xx has the same MDIO bug block as IPQ4019. Add IO range and +clock resources description. + +Reviewed-by: Bryan O'Donoghue +Signed-off-by: Baruch Siach +Signed-off-by: Bjorn Andersson +Link: https://lore.kernel.org/r/ef01a79ccc6ef86dc3a10d0fa3331794d49e9859.1646031524.git.baruch@tkos.co.il +--- + arch/arm64/boot/dts/qcom/ipq6018.dtsi | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/arch/arm64/boot/dts/qcom/ipq6018.dtsi b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +index ce4c2b4a5fc0..5ac0aafc620e 100644 +--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +@@ -623,6 +623,16 @@ qrtr_requests { + }; + }; + ++ mdio: mdio@90000 { ++ #address-cells = <1>; ++ #size-cells = <0>; ++ compatible = "qcom,ipq6018-mdio", "qcom,ipq4019-mdio"; ++ reg = <0x0 0x90000 0x0 0x64>; ++ clocks = <&gcc GCC_MDIO_AHB_CLK>; ++ clock-names = "gcc_mdio_ahb_clk"; ++ status = "disabled"; ++ }; ++ + qusb_phy_1: qusb@59000 { + compatible = "qcom,ipq6018-qusb2-phy"; + reg = <0x0 0x059000 0x0 0x180>; +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1002-clk-qcom-add-support-for-hw-controlled-RCG.patch b/target/linux/ipq60xx/patches-5.15/1002-clk-qcom-add-support-for-hw-controlled-RCG.patch new file mode 100644 index 000000000..d1dfe7a8f --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1002-clk-qcom-add-support-for-hw-controlled-RCG.patch @@ -0,0 +1,150 @@ +From e21cc1fb80e1b220628f565bb003f6682cadea16 Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Tue, 13 Jun 2017 15:30:39 +0530 +Subject: [PATCH 1002/1011] 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. The check for update bit can be skipped in this case. + +Signed-off-by: Abhishek Sahu +Signed-off-by: Praveenkumar I +Signed-off-by: Alexandru Gagniuc +--- + drivers/clk/qcom/clk-rcg.h | 4 ++++ + drivers/clk/qcom/clk-rcg2.c | 29 ++++++++++++++++++++++------- + 2 files changed, 26 insertions(+), 7 deletions(-) + +diff --git a/drivers/clk/qcom/clk-rcg.h b/drivers/clk/qcom/clk-rcg.h +index 248115a018bc..f53fb0acfd93 100644 +--- 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_ops; + * @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; +diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c +index fdbbda1f2ba4..f696aca67162 100644 +--- a/drivers/clk/qcom/clk-rcg2.c ++++ b/drivers/clk/qcom/clk-rcg2.c +@@ -98,7 +98,7 @@ static u8 clk_rcg2_get_parent(struct clk_hw *hw) + 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; +@@ -110,6 +110,9 @@ static int update_config(struct clk_rcg2 *rcg) + 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); +@@ -128,14 +131,19 @@ static int clk_rcg2_set_parent(struct clk_hw *hw, u8 index) + { + 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); + } + + /* +@@ -312,12 +320,19 @@ static int __clk_rcg2_configure(struct clk_rcg2 *rcg, const struct freq_tbl *f) + 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, +@@ -448,7 +463,7 @@ static int clk_rcg2_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty) + if (ret) + return ret; + +- return update_config(rcg); ++ return update_config(rcg, true); + } + + const struct clk_ops clk_rcg2_ops = { +@@ -910,7 +925,7 @@ static int clk_gfx3d_set_rate_and_parent(struct clk_hw *hw, unsigned long rate, + 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, +@@ -1022,7 +1037,7 @@ static int clk_rcg2_shared_enable(struct clk_hw *hw) + if (ret) + return ret; + +- ret = update_config(rcg); ++ ret = update_config(rcg, true); + if (ret) + return ret; + +@@ -1053,7 +1068,7 @@ static void clk_rcg2_shared_disable(struct clk_hw *hw) + 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); + +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1003-clk-qcom-ipq6018-add-missing-clock-flags.patch b/target/linux/ipq60xx/patches-5.15/1003-clk-qcom-ipq6018-add-missing-clock-flags.patch new file mode 100644 index 000000000..1ad472908 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1003-clk-qcom-ipq6018-add-missing-clock-flags.patch @@ -0,0 +1,152 @@ +From a9179aeead3fe0737b47d11ef687edc2dc15c964 Mon Sep 17 00:00:00 2001 +From: Kathiravan T +Date: Fri, 6 Nov 2020 12:18:37 +0530 +Subject: [PATCH 1003/1011] clk: qcom: ipq6018: add missing clock flags + +The downstream QCA 5.4 kernel sets several extra clock flags. From +their reasoning: + +Add the CLK_IS_CRITICAL, CLK_RCG2_HW_CONTROLLED for the required clocks. + +Without the critical flag, below warning is seen when QDSS +components are enabled. + +[ 1.712722] ------------[ cut here ]------------ +[ 1.719039] gcc_qdss_dap_clk status stuck at 'on' +[ 1.719084] WARNING: CPU: 1 PID: 1 at drivers/clk/qcom/clk-branch.c:92 clk_branch_toggle+0x160/0x178 + +Signed-off-by: Kathiravan T +Signed-off-by: Alexandru Gagniuc +--- + drivers/clk/qcom/gcc-ipq6018.c | 19 +++++++++++++++---- + 1 file changed, 15 insertions(+), 4 deletions(-) + +diff --git a/drivers/clk/qcom/gcc-ipq6018.c b/drivers/clk/qcom/gcc-ipq6018.c +index 3f9c2f61a5d9..cf11c9aa5004 100644 +--- a/drivers/clk/qcom/gcc-ipq6018.c ++++ b/drivers/clk/qcom/gcc-ipq6018.c +@@ -62,6 +62,7 @@ static struct clk_alpha_pll gpll0_main = { + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }, + }; +@@ -150,6 +151,7 @@ static struct clk_alpha_pll gpll6_main = { + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }, + }; +@@ -181,6 +183,7 @@ static struct clk_alpha_pll gpll4_main = { + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }, + }; +@@ -211,6 +214,7 @@ static struct clk_rcg2 pcnoc_bfdcd_clk_src = { + .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_data = gcc_xo_gpll0_gpll0_out_main_div2, +@@ -232,6 +236,7 @@ static struct clk_alpha_pll gpll2_main = { + }, + .num_parents = 1, + .ops = &clk_alpha_pll_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }, + }; +@@ -456,6 +461,7 @@ static struct clk_branch gcc_sleep_clk_src = { + }, + .num_parents = 1, + .ops = &clk_branch2_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }, + }; +@@ -960,6 +966,7 @@ static struct clk_rcg2 nss_crypto_clk_src = { + .mnd_width = 16, + .hid_width = 5, + .parent_map = gcc_xo_nss_crypto_pll_gpll0_map, ++ .flags = CLK_RCG2_HW_CONTROLLED, + .clkr.hw.init = &(struct clk_init_data){ + .name = "nss_crypto_clk_src", + .parent_data = gcc_xo_nss_crypto_pll_gpll0, +@@ -1131,6 +1138,7 @@ static struct clk_rcg2 nss_ubi0_clk_src = { + .freq_tbl = ftbl_nss_ubi_clk_src, + .hid_width = 5, + .parent_map = gcc_xo_ubi32_gpll0_gpll2_gpll4_gpll6_map, ++ .flags = CLK_RCG2_HW_CONTROLLED, + .clkr.hw.init = &(struct clk_init_data){ + .name = "nss_ubi0_clk_src", + .parent_data = gcc_xo_ubi32_pll_gpll0_gpll2_gpll4_gpll6, +@@ -1869,7 +1877,7 @@ static struct clk_branch gcc_apss_ahb_clk = { + .parent_hws = (const struct clk_hw *[]){ + &apss_ahb_postdiv_clk_src.clkr.hw }, + .num_parents = 1, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + .ops = &clk_branch2_ops, + }, + }, +@@ -1891,11 +1899,13 @@ static struct clk_rcg2 system_noc_bfdcd_clk_src = { + .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_data = gcc_xo_gpll0_gpll6_gpll0_out_main_div2, + .num_parents = 4, + .ops = &clk_rcg2_ops, ++ .flags = CLK_IS_CRITICAL, + }, + }; + +@@ -1945,7 +1955,7 @@ static struct clk_branch gcc_apss_axi_clk = { + .parent_hws = (const struct clk_hw *[]){ + &apss_axi_clk_src.clkr.hw }, + .num_parents = 1, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + .ops = &clk_branch2_ops, + }, + }, +@@ -2314,7 +2324,7 @@ static struct clk_branch gcc_xo_clk = { + .parent_hws = (const struct clk_hw *[]){ + &gcc_xo_clk_src.clkr.hw }, + .num_parents = 1, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + .ops = &clk_branch2_ops, + }, + }, +@@ -3163,6 +3173,7 @@ static struct clk_branch gcc_nssnoc_ppe_cfg_clk = { + .name = "gcc_nssnoc_ppe_cfg_clk", + .parent_hws = (const struct clk_hw *[]){ + &nss_ppe_clk_src.clkr.hw }, ++ .num_parents = 1, + .flags = CLK_SET_RATE_PARENT, + .ops = &clk_branch2_ops, + }, +@@ -3521,7 +3532,7 @@ static struct clk_branch gcc_qdss_dap_clk = { + .parent_hws = (const struct clk_hw *[]){ + &qdss_dap_sync_clk_src.hw }, + .num_parents = 1, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, + .ops = &clk_branch2_ops, + }, + }, +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1004-clk-qcom-ipq6018-add-missing-clocks.patch b/target/linux/ipq60xx/patches-5.15/1004-clk-qcom-ipq6018-add-missing-clocks.patch new file mode 100644 index 000000000..eaf20b806 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1004-clk-qcom-ipq6018-add-missing-clocks.patch @@ -0,0 +1,750 @@ +From 0c07b4b2683c327c6a7f9012fa0880ddac257514 Mon Sep 17 00:00:00 2001 +From: anusha +Date: Wed, 19 Aug 2020 17:59:22 +0530 +Subject: [PATCH 1004/1011] clk: qcom: ipq6018: add missing clocks + +A large number of clocks are missing for the IPQ6018. Add those clocks +from the downstream QCA 5.4 kernel. + +Signed-off-by: anusha +Signed-off-by: Alexandru Gagniuc +--- + drivers/clk/qcom/gcc-ipq6018.c | 619 ++++++++++++++++++++++++++++++++- + 1 file changed, 603 insertions(+), 16 deletions(-) + +diff --git a/drivers/clk/qcom/gcc-ipq6018.c b/drivers/clk/qcom/gcc-ipq6018.c +index cf11c9aa5004..69d06dd4aeb8 100644 +--- a/drivers/clk/qcom/gcc-ipq6018.c ++++ b/drivers/clk/qcom/gcc-ipq6018.c +@@ -223,6 +223,19 @@ static struct clk_rcg2 pcnoc_bfdcd_clk_src = { + }, + }; + ++static struct clk_fixed_factor pcnoc_clk_src = { ++ .mult = 1, ++ .div = 1, ++ .hw.init = &(struct clk_init_data){ ++ .name = "pcnoc_clk_src", ++ .parent_hws = (const struct clk_hw *[]){ ++ &pcnoc_bfdcd_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .ops = &clk_fixed_factor_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ + static struct clk_alpha_pll gpll2_main = { + .offset = 0x4a000, + .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_DEFAULT], +@@ -505,6 +518,19 @@ static struct clk_rcg2 snoc_nssnoc_bfdcd_clk_src = { + }, + }; + ++static struct clk_fixed_factor snoc_nssnoc_clk_src = { ++ .mult = 1, ++ .div = 1, ++ .hw.init = &(struct clk_init_data){ ++ .name = "snoc_nssnoc_clk_src", ++ .parent_hws = (const struct clk_hw *[]){ ++ &snoc_nssnoc_bfdcd_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .ops = &clk_fixed_factor_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ + static const struct freq_tbl ftbl_apss_ahb_clk_src[] = { + F(24000000, P_XO, 1, 0, 0), + F(25000000, P_GPLL0_DIV2, 16, 0, 0), +@@ -1909,6 +1935,19 @@ static struct clk_rcg2 system_noc_bfdcd_clk_src = { + }, + }; + ++static struct clk_fixed_factor system_noc_clk_src = { ++ .mult = 1, ++ .div = 1, ++ .hw.init = &(struct clk_init_data){ ++ .name = "system_noc_clk_src", ++ .parent_hws = (const struct clk_hw *[]){ ++ &system_noc_bfdcd_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .ops = &clk_fixed_factor_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ + static const struct freq_tbl ftbl_ubi32_mem_noc_bfdcd_clk_src[] = { + F(24000000, P_XO, 1, 0, 0), + F(307670000, P_BIAS_PLL_NSS_NOC, 1.5, 0, 0), +@@ -1944,6 +1983,19 @@ static struct clk_rcg2 ubi32_mem_noc_bfdcd_clk_src = { + }, + }; + ++static struct clk_fixed_factor ubi32_mem_noc_clk_src = { ++ .mult = 1, ++ .div = 1, ++ .hw.init = &(struct clk_init_data){ ++ .name = "ubi32_mem_noc_clk_src", ++ .parent_hws = (const struct clk_hw *[]){ ++ &ubi32_mem_noc_bfdcd_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .ops = &clk_fixed_factor_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++}; ++ + static struct clk_branch gcc_apss_axi_clk = { + .halt_reg = 0x46020, + .halt_check = BRANCH_HALT_VOTED, +@@ -2138,6 +2190,22 @@ static struct clk_branch gcc_blsp1_qup5_spi_apps_clk = { + }, + }; + ++static struct clk_branch gcc_blsp1_qup6_i2c_apps_clk = { ++ .halt_reg = 0x07010, ++ .clkr = { ++ .enable_reg = 0x07010, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_blsp1_qup6_i2c_apps_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &blsp1_qup6_i2c_apps_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ + static struct clk_branch gcc_blsp1_qup6_spi_apps_clk = { + .halt_reg = 0x0700c, + .clkr = { +@@ -2681,6 +2749,454 @@ static struct clk_rcg2 lpass_q6_axim_clk_src = { + }, + }; + ++static struct clk_branch gcc_wcss_axi_m_clk = { ++ .halt_reg = 0x5903C, ++ .clkr = { ++ .enable_reg = 0x5903C, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_axi_m_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &system_noc_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_sys_noc_wcss_ahb_clk = { ++ .halt_reg = 0x26034, ++ .clkr = { ++ .enable_reg = 0x26034, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_sys_noc_wcss_ahb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &wcss_ahb_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6_axim_clk = { ++ .halt_reg = 0x5913C, ++ .clkr = { ++ .enable_reg = 0x5913C, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6_axim_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &q6_axi_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6ss_atbm_clk = { ++ .halt_reg = 0x59144, ++ .clkr = { ++ .enable_reg = 0x59144, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6ss_atbm_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_at_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6ss_pclkdbg_clk = { ++ .halt_reg = 0x59140, ++ .clkr = { ++ .enable_reg = 0x59140, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6ss_pclkdbg_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_dap_sync_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6_tsctr_1to2_clk = { ++ .halt_reg = 0x59148, ++ .clkr = { ++ .enable_reg = 0x59148, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6_tsctr_1to2_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_tsctr_div2_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_core_tbu_clk = { ++ .halt_reg = 0x12028, ++ .clkr = { ++ .enable_reg = 0xb00c, ++ .enable_mask = BIT(7), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_core_tbu_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &system_noc_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_q6_tbu_clk = { ++ .halt_reg = 0x1202C, ++ .clkr = { ++ .enable_reg = 0xb00c, ++ .enable_mask = BIT(8), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_q6_tbu_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &q6_axi_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6_axim2_clk = { ++ .halt_reg = 0x59150, ++ .clkr = { ++ .enable_reg = 0x59150, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6_axim2_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &q6_axi_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6_ahb_clk = { ++ .halt_reg = 0x59138, ++ .clkr = { ++ .enable_reg = 0x59138, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6_ahb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &wcss_ahb_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_q6_ahb_s_clk = { ++ .halt_reg = 0x5914C, ++ .clkr = { ++ .enable_reg = 0x5914C, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_q6_ahb_s_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &wcss_ahb_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_apb_clk = { ++ .halt_reg = 0x59040, ++ .clkr = { ++ .enable_reg = 0x59040, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_apb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_dap_sync_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_atb_clk = { ++ .halt_reg = 0x59044, ++ .clkr = { ++ .enable_reg = 0x59044, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_atb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_at_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_nts_clk = { ++ .halt_reg = 0x59048, ++ .clkr = { ++ .enable_reg = 0x59048, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_nts_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_tsctr_div2_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_dapbus_clk = { ++ .halt_reg = 0x5905C, ++ .clkr = { ++ .enable_reg = 0x5905C, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_dapbus_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_dap_sync_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_apb_bdg_clk = { ++ .halt_reg = 0x59050, ++ .clkr = { ++ .enable_reg = 0x59050, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_apb_bdg_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_dap_sync_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_atb_bdg_clk = { ++ .halt_reg = 0x59054, ++ .clkr = { ++ .enable_reg = 0x59054, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_atb_bdg_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_at_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_nts_bdg_clk = { ++ .halt_reg = 0x59058, ++ .clkr = { ++ .enable_reg = 0x59058, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_nts_bdg_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_tsctr_div2_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_dbg_ifc_dapbus_bdg_clk = { ++ .halt_reg = 0x59060, ++ .clkr = { ++ .enable_reg = 0x59060, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_dbg_ifc_dapbus_bdg_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_dap_sync_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_nssnoc_atb_clk = { ++ .halt_reg = 0x6818C, ++ .clkr = { ++ .enable_reg = 0x6818C, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_nssnoc_atb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_at_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_ecahb_clk = { ++ .halt_reg = 0x59038, ++ .clkr = { ++ .enable_reg = 0x59038, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_ecahb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &wcss_ahb_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_acmt_clk = { ++ .halt_reg = 0x59064, ++ .clkr = { ++ .enable_reg = 0x59064, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_acmt_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &wcss_ahb_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_wcss_ahb_s_clk = { ++ .halt_reg = 0x59034, ++ .clkr = { ++ .enable_reg = 0x59034, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_wcss_ahb_s_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &wcss_ahb_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++struct clk_branch gcc_rbcpr_wcss_ahb_clk = { ++ .halt_reg = 0x3A008, ++ .clkr = { ++ .enable_reg = 0x3A008, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_rbcpr_wcss_ahb_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &pcnoc_clk_src.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++struct clk_branch gcc_mem_noc_q6_axi_clk = { ++ .halt_reg = 0x1D038, ++ .clkr = { ++ .enable_reg = 0x1D038, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_mem_noc_q6_axi_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &q6_axi_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_sys_noc_qdss_stm_axi_clk = { ++ .halt_reg = 0x26024, ++ .clkr = { ++ .enable_reg = 0x26024, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_sys_noc_qdss_stm_axi_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_stm_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_qdss_stm_clk = { ++ .halt_reg = 0x29044, ++ .clkr = { ++ .enable_reg = 0x29044, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_qdss_stm_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_stm_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gcc_qdss_traceclkin_clk = { ++ .halt_reg = 0x29060, ++ .clkr = { ++ .enable_reg = 0x29060, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_qdss_traceclkin_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_traceclkin_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ + static struct freq_tbl ftbl_rbcpr_wcss_clk_src[] = { + F(24000000, P_XO, 1, 0, 0), + F(50000000, P_GPLL0, 16, 0, 0), +@@ -2700,6 +3216,23 @@ static struct clk_rcg2 rbcpr_wcss_clk_src = { + }, + }; + ++struct clk_branch gcc_rbcpr_wcss_clk = { ++ .halt_reg = 0x3A004, ++ .clkr = { ++ .enable_reg = 0x3A004, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_rbcpr_wcss_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &rbcpr_wcss_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ ++ + static struct clk_branch gcc_lpass_core_axim_clk = { + .halt_reg = 0x1F028, + .clkr = { +@@ -3522,6 +4055,22 @@ static struct clk_branch gcc_prng_ahb_clk = { + }, + }; + ++static struct clk_branch gcc_qdss_at_clk = { ++ .halt_reg = 0x29024, ++ .clkr = { ++ .enable_reg = 0x29024, ++ .enable_mask = BIT(0), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gcc_qdss_at_clk", ++ .parent_hws = (const struct clk_hw *[]){ ++ &qdss_at_clk_src.clkr.hw }, ++ .num_parents = 1, ++ .flags = CLK_SET_RATE_PARENT | CLK_IS_CRITICAL, ++ .ops = &clk_branch2_ops, ++ }, ++ }, ++}; ++ + static struct clk_branch gcc_qdss_dap_clk = { + .halt_reg = 0x29084, + .clkr = { +@@ -4195,6 +4744,9 @@ static struct clk_hw *gcc_ipq6018_hws[] = { + &gpll6_out_main_div2.hw, + &qdss_dap_sync_clk_src.hw, + &qdss_tsctr_div2_clk_src.hw, ++ &pcnoc_clk_src.hw, ++ &snoc_nssnoc_clk_src.hw, ++ &ubi32_mem_noc_clk_src.hw, + }; + + static struct clk_regmap *gcc_ipq6018_clks[] = { +@@ -4292,6 +4844,7 @@ static struct clk_regmap *gcc_ipq6018_clks[] = { + [GCC_BLSP1_QUP4_SPI_APPS_CLK] = &gcc_blsp1_qup4_spi_apps_clk.clkr, + [GCC_BLSP1_QUP5_I2C_APPS_CLK] = &gcc_blsp1_qup5_i2c_apps_clk.clkr, + [GCC_BLSP1_QUP5_SPI_APPS_CLK] = &gcc_blsp1_qup5_spi_apps_clk.clkr, ++ [GCC_BLSP1_QUP6_I2C_APPS_CLK] = &gcc_blsp1_qup6_i2c_apps_clk.clkr, + [GCC_BLSP1_QUP6_SPI_APPS_CLK] = &gcc_blsp1_qup6_spi_apps_clk.clkr, + [GCC_BLSP1_UART1_APPS_CLK] = &gcc_blsp1_uart1_apps_clk.clkr, + [GCC_BLSP1_UART2_APPS_CLK] = &gcc_blsp1_uart2_apps_clk.clkr, +@@ -4357,6 +4910,7 @@ static struct clk_regmap *gcc_ipq6018_clks[] = { + [GCC_SYS_NOC_PCIE0_AXI_CLK] = &gcc_sys_noc_pcie0_axi_clk.clkr, + [GCC_PCIE0_PIPE_CLK] = &gcc_pcie0_pipe_clk.clkr, + [GCC_PRNG_AHB_CLK] = &gcc_prng_ahb_clk.clkr, ++ [GCC_QDSS_AT_CLK] = &gcc_qdss_at_clk.clkr, + [GCC_QDSS_DAP_CLK] = &gcc_qdss_dap_clk.clkr, + [GCC_QPIC_AHB_CLK] = &gcc_qpic_ahb_clk.clkr, + [GCC_QPIC_CLK] = &gcc_qpic_clk.clkr, +@@ -4398,9 +4952,35 @@ static struct clk_regmap *gcc_ipq6018_clks[] = { + [PCIE0_RCHNG_CLK_SRC] = &pcie0_rchng_clk_src.clkr, + [GCC_PCIE0_AXI_S_BRIDGE_CLK] = &gcc_pcie0_axi_s_bridge_clk.clkr, + [PCIE0_RCHNG_CLK] = &gcc_pcie0_rchng_clk.clkr, ++ [GCC_WCSS_AXI_M_CLK] = &gcc_wcss_axi_m_clk.clkr, ++ [GCC_SYS_NOC_WCSS_AHB_CLK] = &gcc_sys_noc_wcss_ahb_clk.clkr, + [WCSS_AHB_CLK_SRC] = &wcss_ahb_clk_src.clkr, ++ [GCC_Q6_AXIM_CLK] = &gcc_q6_axim_clk.clkr, + [Q6_AXI_CLK_SRC] = &q6_axi_clk_src.clkr, ++ [GCC_Q6SS_ATBM_CLK] = &gcc_q6ss_atbm_clk.clkr, ++ [GCC_Q6SS_PCLKDBG_CLK] = &gcc_q6ss_pclkdbg_clk.clkr, ++ [GCC_Q6_TSCTR_1TO2_CLK] = &gcc_q6_tsctr_1to2_clk.clkr, ++ [GCC_WCSS_CORE_TBU_CLK] = &gcc_wcss_core_tbu_clk.clkr, ++ [GCC_WCSS_Q6_TBU_CLK] = &gcc_wcss_q6_tbu_clk.clkr, ++ [GCC_Q6_AXIM2_CLK] = &gcc_q6_axim2_clk.clkr, ++ [GCC_Q6_AHB_CLK] = &gcc_q6_ahb_clk.clkr, ++ [GCC_Q6_AHB_S_CLK] = &gcc_q6_ahb_s_clk.clkr, ++ [GCC_WCSS_DBG_IFC_APB_CLK] = &gcc_wcss_dbg_ifc_apb_clk.clkr, ++ [GCC_WCSS_DBG_IFC_ATB_CLK] = &gcc_wcss_dbg_ifc_atb_clk.clkr, ++ [GCC_WCSS_DBG_IFC_NTS_CLK] = &gcc_wcss_dbg_ifc_nts_clk.clkr, ++ [GCC_WCSS_DBG_IFC_DAPBUS_CLK] = &gcc_wcss_dbg_ifc_dapbus_clk.clkr, ++ [GCC_WCSS_DBG_IFC_APB_BDG_CLK] = &gcc_wcss_dbg_ifc_apb_bdg_clk.clkr, ++ [GCC_WCSS_DBG_IFC_ATB_BDG_CLK] = &gcc_wcss_dbg_ifc_atb_bdg_clk.clkr, ++ [GCC_WCSS_DBG_IFC_NTS_BDG_CLK] = &gcc_wcss_dbg_ifc_nts_bdg_clk.clkr, ++ [GCC_WCSS_DBG_IFC_DAPBUS_BDG_CLK] = &gcc_wcss_dbg_ifc_dapbus_bdg_clk.clkr, ++ [GCC_NSSNOC_ATB_CLK] = &gcc_nssnoc_atb_clk.clkr, ++ [GCC_WCSS_ECAHB_CLK] = &gcc_wcss_ecahb_clk.clkr, ++ [GCC_WCSS_ACMT_CLK] = &gcc_wcss_acmt_clk.clkr, ++ [GCC_WCSS_AHB_S_CLK] = &gcc_wcss_ahb_s_clk.clkr, ++ [GCC_RBCPR_WCSS_CLK] = &gcc_rbcpr_wcss_clk.clkr, + [RBCPR_WCSS_CLK_SRC] = &rbcpr_wcss_clk_src.clkr, ++ [GCC_RBCPR_WCSS_AHB_CLK] = &gcc_rbcpr_wcss_ahb_clk.clkr, ++ [GCC_MEM_NOC_Q6_AXI_CLK] = &gcc_mem_noc_q6_axi_clk.clkr, + [GCC_LPASS_CORE_AXIM_CLK] = &gcc_lpass_core_axim_clk.clkr, + [LPASS_CORE_AXIM_CLK_SRC] = &lpass_core_axim_clk_src.clkr, + [GCC_LPASS_SNOC_CFG_CLK] = &gcc_lpass_snoc_cfg_clk.clkr, +@@ -4416,6 +4996,9 @@ static struct clk_regmap *gcc_ipq6018_clks[] = { + [GCC_MEM_NOC_UBI32_CLK] = &gcc_mem_noc_ubi32_clk.clkr, + [GCC_MEM_NOC_LPASS_CLK] = &gcc_mem_noc_lpass_clk.clkr, + [GCC_SNOC_LPASS_CFG_CLK] = &gcc_snoc_lpass_cfg_clk.clkr, ++ [GCC_SYS_NOC_QDSS_STM_AXI_CLK] = &gcc_sys_noc_qdss_stm_axi_clk.clkr, ++ [GCC_QDSS_STM_CLK] = &gcc_qdss_stm_clk.clkr, ++ [GCC_QDSS_TRACECLKIN_CLK] = &gcc_qdss_traceclkin_clk.clkr, + [QDSS_STM_CLK_SRC] = &qdss_stm_clk_src.clkr, + [QDSS_TRACECLKIN_CLK_SRC] = &qdss_traceclkin_clk_src.clkr, + }; +@@ -4528,24 +5111,24 @@ static const struct qcom_reset_map gcc_ipq6018_resets[] = { + [GCC_PCIE0_AHB_ARES] = { 0x75040, 5 }, + [GCC_PCIE0_AXI_MASTER_STICKY_ARES] = { 0x75040, 6 }, + [GCC_PCIE0_AXI_SLAVE_STICKY_ARES] = { 0x75040, 7 }, +- [GCC_PPE_FULL_RESET] = { 0x68014, 0 }, +- [GCC_UNIPHY0_SOFT_RESET] = { 0x56004, 0 }, ++ [GCC_PPE_FULL_RESET] = { 0x68014, 0, 0xf0000}, ++ [GCC_UNIPHY0_SOFT_RESET] = { 0x56004, 0, 0x3ff2}, + [GCC_UNIPHY0_XPCS_RESET] = { 0x56004, 2 }, +- [GCC_UNIPHY1_SOFT_RESET] = { 0x56104, 0 }, ++ [GCC_UNIPHY1_SOFT_RESET] = { 0x56104, 0, 0x32}, + [GCC_UNIPHY1_XPCS_RESET] = { 0x56104, 2 }, +- [GCC_EDMA_HW_RESET] = { 0x68014, 0 }, +- [GCC_NSSPORT1_RESET] = { 0x68014, 0 }, +- [GCC_NSSPORT2_RESET] = { 0x68014, 0 }, +- [GCC_NSSPORT3_RESET] = { 0x68014, 0 }, +- [GCC_NSSPORT4_RESET] = { 0x68014, 0 }, +- [GCC_NSSPORT5_RESET] = { 0x68014, 0 }, +- [GCC_UNIPHY0_PORT1_ARES] = { 0x56004, 0 }, +- [GCC_UNIPHY0_PORT2_ARES] = { 0x56004, 0 }, +- [GCC_UNIPHY0_PORT3_ARES] = { 0x56004, 0 }, +- [GCC_UNIPHY0_PORT4_ARES] = { 0x56004, 0 }, +- [GCC_UNIPHY0_PORT5_ARES] = { 0x56004, 0 }, +- [GCC_UNIPHY0_PORT_4_5_RESET] = { 0x56004, 0 }, +- [GCC_UNIPHY0_PORT_4_RESET] = { 0x56004, 0 }, ++ [GCC_EDMA_HW_RESET] = { 0x68014, 0, 0x300000}, ++ [GCC_NSSPORT1_RESET] = { 0x68014, 0, 0x1000003}, ++ [GCC_NSSPORT2_RESET] = { 0x68014, 0, 0x200000c}, ++ [GCC_NSSPORT3_RESET] = { 0x68014, 0, 0x4000030}, ++ [GCC_NSSPORT4_RESET] = { 0x68014, 0, 0x8000300}, ++ [GCC_NSSPORT5_RESET] = { 0x68014, 0, 0x10000c00}, ++ [GCC_UNIPHY0_PORT1_ARES] = { 0x56004, 0, 0x30}, ++ [GCC_UNIPHY0_PORT2_ARES] = { 0x56004, 0, 0xc0}, ++ [GCC_UNIPHY0_PORT3_ARES] = { 0x56004, 0, 0x300}, ++ [GCC_UNIPHY0_PORT4_ARES] = { 0x56004, 0, 0xc00}, ++ [GCC_UNIPHY0_PORT5_ARES] = { 0x56004, 0, 0x3000}, ++ [GCC_UNIPHY0_PORT_4_5_RESET] = { 0x56004, 0, 0x3c02}, ++ [GCC_UNIPHY0_PORT_4_RESET] = { 0x56004, 0, 0xc02}, + [GCC_LPASS_BCR] = {0x1F000, 0}, + [GCC_UBI32_TBU_BCR] = {0x65000, 0}, + [GCC_LPASS_TBU_BCR] = {0x6C000, 0}, +@@ -4597,6 +5180,10 @@ static const struct qcom_cc_desc gcc_ipq6018_desc = { + static int gcc_ipq6018_probe(struct platform_device *pdev) + { + struct regmap *regmap; ++ struct device *dev = &pdev->dev; ++ ++ clk_register_fixed_rate(dev, "pcie20_phy0_pipe_clk", NULL, 0, ++ 250000000); + + regmap = qcom_cc_map(pdev, &gcc_ipq6018_desc); + if (IS_ERR(regmap)) +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1005-clk-qcom-ipq6018-update-Huayra-PLL-settings.patch b/target/linux/ipq60xx/patches-5.15/1005-clk-qcom-ipq6018-update-Huayra-PLL-settings.patch new file mode 100644 index 000000000..edbfa9d7d --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1005-clk-qcom-ipq6018-update-Huayra-PLL-settings.patch @@ -0,0 +1,61 @@ +From c027fd431bb2326d03d21a57e9f59f7ae414abbf Mon Sep 17 00:00:00 2001 +From: Praveenkumar I +Date: Tue, 12 Nov 2019 10:25:25 +0530 +Subject: [PATCH 1005/1011] clk: qcom: ipq6018: update Huayra PLL settings + +1. Updated APSS PLL and UBI PLL settings recommended by ATE. +2. Added TEST_CTL_VAL configuration for Huayra PLL + +Signed-off-by: Praveenkumar I +Signed-off-by: Alexandru Gagniuc +--- + drivers/clk/qcom/clk-alpha-pll.c | 6 ++++++ + drivers/clk/qcom/gcc-ipq6018.c | 9 ++++++--- + 2 files changed, 12 insertions(+), 3 deletions(-) + +diff --git a/drivers/clk/qcom/clk-alpha-pll.c b/drivers/clk/qcom/clk-alpha-pll.c +index 8f65b9bdafce..6179f2404216 100644 +--- a/drivers/clk/qcom/clk-alpha-pll.c ++++ b/drivers/clk/qcom/clk-alpha-pll.c +@@ -287,6 +287,12 @@ void clk_alpha_pll_configure(struct clk_alpha_pll *pll, struct regmap *regmap, + + regmap_update_bits(regmap, PLL_USER_CTL(pll), mask, val); + ++ if (config->test_ctl_val != 0) ++ regmap_write(regmap, PLL_TEST_CTL(pll), config->test_ctl_val); ++ ++ if (config->test_ctl_hi_val != 0) ++ regmap_write(regmap, PLL_TEST_CTL_U(pll), config->test_ctl_hi_val); ++ + if (pll->flags & SUPPORTS_FSM_MODE) + qcom_pll_set_fsm_mode(regmap, PLL_MODE(pll), 6, 0); + } +diff --git a/drivers/clk/qcom/gcc-ipq6018.c b/drivers/clk/qcom/gcc-ipq6018.c +index 69d06dd4aeb8..98f0e950f5c1 100644 +--- a/drivers/clk/qcom/gcc-ipq6018.c ++++ b/drivers/clk/qcom/gcc-ipq6018.c +@@ -4711,15 +4711,18 @@ static struct clk_branch gcc_dcc_clk = { + + static const struct alpha_pll_config ubi32_pll_config = { + .l = 0x3e, +- .alpha = 0x57, +- .config_ctl_val = 0x240d6aa8, +- .config_ctl_hi_val = 0x3c2, ++ .alpha = 0x6667, ++ .config_ctl_val = 0x240d4828, ++ .config_ctl_hi_val = 0x6, + .main_output_mask = BIT(0), + .aux_output_mask = BIT(1), + .pre_div_val = 0x0, + .pre_div_mask = BIT(12), + .post_div_val = 0x0, + .post_div_mask = GENMASK(9, 8), ++ .alpha_en_mask = BIT(24), ++ .test_ctl_val = 0x1C0000C0, ++ .test_ctl_hi_val = 0x4000, + }; + + static const struct alpha_pll_config nss_crypto_pll_config = { +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1006-clk-qcom-ipq6018-openwrt-update-parent-mapping-for-p.patch b/target/linux/ipq60xx/patches-5.15/1006-clk-qcom-ipq6018-openwrt-update-parent-mapping-for-p.patch new file mode 100644 index 000000000..ec63b5d57 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1006-clk-qcom-ipq6018-openwrt-update-parent-mapping-for-p.patch @@ -0,0 +1,67 @@ +From 3ca697b006bf7790a0dc158944808221bf36693c Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +Date: Sat, 27 Aug 2022 22:56:57 -0500 +Subject: [PATCH 1006/1011] clk: qcom: ipq6018: openwrt: update parent mapping + for port clocks + +According to our resident expert on Qualcomm IPQ: + + " Ughh, they messed up like they always do, and instead of fixing + that they just hack it. + + I assume they messed up the parenting as they only set fw_name + which is locally available, but SSDK is trying to set parenting + manually without them being available as you need to pass them by + DT node. + + So, what probably needs to be done instead of hacking it together + and itself is a workaround for a lack of proper switch driver is + to also set the .name property so it will still fallback to global + clock names + " + +To allow the SSDK to set clock parents, also set the ".name" field of +uniphy clocks, as suggested by the expert. + +Signed-off-by: Alexandru Gagniuc +--- + drivers/clk/qcom/gcc-ipq6018.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/drivers/clk/qcom/gcc-ipq6018.c b/drivers/clk/qcom/gcc-ipq6018.c +index 98f0e950f5c1..17141c139685 100644 +--- a/drivers/clk/qcom/gcc-ipq6018.c ++++ b/drivers/clk/qcom/gcc-ipq6018.c +@@ -567,10 +567,10 @@ static const struct freq_tbl ftbl_nss_port5_rx_clk_src[] = { + static const struct clk_parent_data + gcc_xo_uniphy0_rx_tx_uniphy1_rx_tx_ubi32_bias[] = { + { .fw_name = "xo" }, +- { .fw_name = "uniphy0_gcc_rx_clk" }, +- { .fw_name = "uniphy0_gcc_tx_clk" }, +- { .fw_name = "uniphy1_gcc_rx_clk" }, +- { .fw_name = "uniphy1_gcc_tx_clk" }, ++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" }, ++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" }, ++ { .fw_name = "uniphy1_gcc_rx_clk", .name = "uniphy1_gcc_rx_clk" }, ++ { .fw_name = "uniphy1_gcc_tx_clk", .name = "uniphy1_gcc_tx_clk" }, + { .hw = &ubi32_pll.clkr.hw }, + { .fw_name = "bias_pll_cc_clk" }, + }; +@@ -614,10 +614,10 @@ static const struct freq_tbl ftbl_nss_port5_tx_clk_src[] = { + static const struct clk_parent_data + gcc_xo_uniphy0_tx_rx_uniphy1_tx_rx_ubi32_bias[] = { + { .fw_name = "xo" }, +- { .fw_name = "uniphy0_gcc_tx_clk" }, +- { .fw_name = "uniphy0_gcc_rx_clk" }, +- { .fw_name = "uniphy1_gcc_tx_clk" }, +- { .fw_name = "uniphy1_gcc_rx_clk" }, ++ { .fw_name = "uniphy0_gcc_tx_clk", .name = "uniphy0_gcc_tx_clk" }, ++ { .fw_name = "uniphy0_gcc_rx_clk", .name = "uniphy0_gcc_rx_clk" }, ++ { .fw_name = "uniphy1_gcc_tx_clk", .name = "uniphy1_gcc_tx_clk" }, ++ { .fw_name = "uniphy1_gcc_rx_clk", .name = "uniphy1_gcc_rx_clk" }, + { .hw = &ubi32_pll.clkr.hw }, + { .fw_name = "bias_pll_cc_clk" }, + }; +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1007-arm64-dts-qcom-ipq6018-Add-missing-fixed-clocks.patch b/target/linux/ipq60xx/patches-5.15/1007-arm64-dts-qcom-ipq6018-Add-missing-fixed-clocks.patch new file mode 100644 index 000000000..2aa0011c3 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1007-arm64-dts-qcom-ipq6018-Add-missing-fixed-clocks.patch @@ -0,0 +1,53 @@ +From b0da605ab258c02e0ab725a8014bfd961b36bed5 Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +Date: Sat, 27 Aug 2022 17:33:37 -0500 +Subject: [PATCH 1007/1011] arm64: dts: qcom: ipq6018: Add missing fixed-clocks + +Signed-off-by: Alexandru Gagniuc +--- + arch/arm64/boot/dts/qcom/ipq6018.dtsi | 22 ++++++++++++++++++++-- + 1 file changed, 20 insertions(+), 2 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq6018.dtsi b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +index 5ac0aafc620e..a9cc92ed03eb 100644 +--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +@@ -27,6 +27,18 @@ xo: xo { + clock-frequency = <24000000>; + #clock-cells = <0>; + }; ++ ++ bias_pll_cc_clk: bias-pll-cc-clk { ++ compatible = "fixed-clock"; ++ clock-frequency = <300000000>; ++ #clock-cells = <0>; ++ }; ++ ++ bias_pll_nss_noc_clk: bias-pll-nss-noc-clk { ++ compatible = "fixed-clock"; ++ clock-frequency = <416500000>; ++ #clock-cells = <0>; ++ }; + }; + + cpus: cpus { +@@ -247,8 +259,14 @@ qpic_pins: qpic-pins { + gcc: gcc@1800000 { + compatible = "qcom,gcc-ipq6018"; + reg = <0x0 0x01800000 0x0 0x80000>; +- clocks = <&xo>, <&sleep_clk>; +- clock-names = "xo", "sleep_clk"; ++ clocks = <&xo>, ++ <&sleep_clk>, ++ <&bias_pll_cc_clk>, ++ <&bias_pll_nss_noc_clk>; ++ clock-names = "xo", ++ "sleep_clk", ++ "bias_pll_cc_clk", ++ "bias_pll_nss_noc_clk"; + #clock-cells = <1>; + #reset-cells = <1>; + }; +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1008-remoteproc-wcss-Add-ipq6018-support-in-remoteproc.patch b/target/linux/ipq60xx/patches-5.15/1008-remoteproc-wcss-Add-ipq6018-support-in-remoteproc.patch new file mode 100644 index 000000000..247ec31b7 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1008-remoteproc-wcss-Add-ipq6018-support-in-remoteproc.patch @@ -0,0 +1,65 @@ +From f277b1421cdc4d2a6ef4bbb6bcaeb07a099157a2 Mon Sep 17 00:00:00 2001 +From: anusha +Date: Wed, 5 Aug 2020 11:01:41 +0530 +Subject: [PATCH 1008/1011] remoteproc: wcss: Add ipq6018 support in remoteproc + +Signed-off-by: anusha +--- + drivers/remoteproc/qcom_q6v5_wcss.c | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 92c240976f55..1c33f7878736 100644 +--- a/drivers/remoteproc/qcom_q6v5_wcss.c ++++ b/drivers/remoteproc/qcom_q6v5_wcss.c +@@ -91,7 +91,7 @@ + #define WCNSS_PAS_ID 6 + + enum { +- WCSS_IPQ8074, ++ WCSS_IPQ, + WCSS_QCS404, + }; + +@@ -902,7 +902,7 @@ static int q6v5_wcss_init_mmio(struct q6v5_wcss *wcss, + if (!wcss->reg_base) + return -ENOMEM; + +- if (wcss->version == WCSS_IPQ8074) { ++ if (wcss->version == WCSS_IPQ) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rmb"); + wcss->rmb_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wcss->rmb_base)) +@@ -1173,6 +1173,21 @@ static int q6v5_wcss_remove(struct platform_device *pdev) + return 0; + } + ++static const struct wcss_data wcss_ipq6018_res_init = { ++ .init_clock = ipq8074_init_clock, ++ .q6_firmware_name = "IPQ6018/q6_fw.mdt", ++ .m3_firmware_name = "IPQ6018/m3_fw.mdt", ++ .crash_reason_smem = WCSS_CRASH_REASON, ++ .aon_reset_required = true, ++ .wcss_q6_reset_required = true, ++ .bcr_reset_required = false, ++ .ssr_name = "q6wcss", ++ .ops = &q6v5_wcss_ipq8074_ops, ++ .requires_force_stop = false, ++ .need_mem_protection = true, ++ .need_auto_boot = false, ++}; ++ + static const struct wcss_data wcss_ipq8074_res_init = { + .init_clock = ipq8074_init_clock, + .q6_firmware_name = "IPQ8074/q6_fw.mdt", +@@ -1206,6 +1221,7 @@ static const struct wcss_data wcss_qcs404_res_init = { + }; + + static const struct of_device_id q6v5_wcss_of_match[] = { ++ { .compatible = "qcom,ipq6018-wcss-pil", .data = &wcss_ipq6018_res_init }, + { .compatible = "qcom,ipq8074-wcss-pil", .data = &wcss_ipq8074_res_init }, + { .compatible = "qcom,qcs404-wcss-pil", .data = &wcss_qcs404_res_init }, + { }, +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1009-ipq6018-rproc-Add-non-secure-Q6-bringup-sequence.patch b/target/linux/ipq60xx/patches-5.15/1009-ipq6018-rproc-Add-non-secure-Q6-bringup-sequence.patch new file mode 100644 index 000000000..550e022b4 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1009-ipq6018-rproc-Add-non-secure-Q6-bringup-sequence.patch @@ -0,0 +1,472 @@ +From bbde72dea11689b21cc34f61f064d0e35b660fec Mon Sep 17 00:00:00 2001 +From: Manikanta Mylavarapu +Date: Fri, 31 Dec 2021 17:13:59 +0530 +Subject: [PATCH 1009/1011] ipq6018: rproc: Add non secure Q6 bringup sequence + +This patch adds Q6 bring up sequence support. + +Change-Id: I28eee991168034cc240d863e736ed9c766ec4f33 +Signed-off-by: Manikanta Mylavarapu +--- + arch/arm64/boot/dts/qcom/ipq6018.dtsi | 20 ++- + drivers/remoteproc/qcom_q6v5_wcss.c | 223 ++++++++++++++++++++++++-- + 2 files changed, 227 insertions(+), 16 deletions(-) + +diff --git a/arch/arm64/boot/dts/qcom/ipq6018.dtsi b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +index a9cc92ed03eb..d8da83aa8bda 100644 +--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi ++++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi +@@ -618,8 +618,24 @@ q6v5_wcss: remoteproc@cd00000 { + "wcss_reset", + "wcss_q6_reset"; + +- clocks = <&gcc GCC_PRNG_AHB_CLK>; +- clock-names = "prng"; ++ clocks = <&gcc GCC_PRNG_AHB_CLK>, ++ <&gcc GCC_SYS_NOC_WCSS_AHB_CLK>, ++ <&gcc GCC_Q6SS_ATBM_CLK>, ++ <&gcc GCC_Q6SS_PCLKDBG_CLK>, ++ <&gcc GCC_Q6_TSCTR_1TO2_CLK>; ++ clock-names = "prng", ++ "gcc_sys_noc_wcss_ahb_clk", ++ "gcc_q6ss_atbm_clk", ++ "gcc_q6ss_pclkdbg_clk", ++ "gcc_q6_tsctr_1to2_clk"; ++ assigned-clocks = <&gcc GCC_SYS_NOC_WCSS_AHB_CLK>, ++ <&gcc GCC_Q6SS_PCLKDBG_CLK>, ++ <&gcc GCC_Q6_TSCTR_1TO2_CLK>, ++ <&gcc GCC_Q6SS_ATBM_CLK>; ++ assigned-clock-rates = <133333333>, ++ <600000000>, ++ <600000000>, ++ <240000000>; + + qcom,halt-regs = <&tcsr 0x18000 0x1b000 0xe000>; + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 1c33f7878736..d07b710c42db 100644 +--- a/drivers/remoteproc/qcom_q6v5_wcss.c ++++ b/drivers/remoteproc/qcom_q6v5_wcss.c +@@ -27,6 +27,7 @@ + + /* Q6SS Register Offsets */ + #define Q6SS_RESET_REG 0x014 ++#define Q6SS_DBG_CFG 0x018 + #define Q6SS_GFMUX_CTL_REG 0x020 + #define Q6SS_PWR_CTL_REG 0x030 + #define Q6SS_MEM_PWR_CTL 0x0B0 +@@ -68,6 +69,7 @@ + #define HALT_CHECK_MAX_LOOPS 200 + #define Q6SS_XO_CBCR GENMASK(5, 3) + #define Q6SS_SLEEP_CBCR GENMASK(5, 2) ++#define Q6SS_TIMEOUT_US 1000 + + /* Q6SS config/status registers */ + #define TCSR_GLOBAL_CFG0 0x0 +@@ -78,6 +80,7 @@ + #define Q6SS_RST_EVB 0x10 + + #define BHS_EN_REST_ACK BIT(0) ++#define WCSS_HM_RET BIT(1) + #define SSCAON_ENABLE BIT(13) + #define SSCAON_BUS_EN BIT(15) + #define SSCAON_BUS_MUX_MASK GENMASK(18, 16) +@@ -120,6 +123,11 @@ struct q6v5_wcss { + struct clk *qdsp6ss_core_gfmux; + struct clk *lcc_bcr_sleep; + struct clk *prng_clk; ++ struct clk *gcc_sys_noc_wcss_ahb_clk; ++ struct clk *gcc_q6ss_atbm_clk; ++ struct clk *gcc_q6ss_pclkdbg_clk; ++ struct clk *gcc_q6_tsctr_1to2_clk; ++ + struct regulator *cx_supply; + struct qcom_sysmon *sysmon; + +@@ -164,12 +172,77 @@ struct wcss_data { + bool need_auto_boot; + }; + ++static const struct wcss_data wcss_ipq6018_res_init; ++ ++static int ipq6018_clks_prepare_enable(struct q6v5_wcss *wcss) ++{ ++ int ret; ++ ++ ret = clk_prepare_enable(wcss->gcc_sys_noc_wcss_ahb_clk); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(wcss->gcc_q6ss_atbm_clk); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(wcss->gcc_q6ss_pclkdbg_clk); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(wcss->gcc_q6_tsctr_1to2_clk); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static void ipq6018_clks_prepare_disable(struct q6v5_wcss *wcss) ++{ ++ clk_disable_unprepare(wcss->gcc_sys_noc_wcss_ahb_clk); ++ clk_disable_unprepare(wcss->gcc_q6ss_atbm_clk); ++ clk_disable_unprepare(wcss->gcc_q6ss_pclkdbg_clk); ++ clk_disable_unprepare(wcss->gcc_q6_tsctr_1to2_clk); ++} ++ + static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + { ++ const struct wcss_data *desc; + int ret; + u32 val; + int i; + ++ desc = device_get_match_data(wcss->dev); ++ if (desc == &wcss_ipq6018_res_init) { ++ if (desc->aon_reset_required) { ++ /* Deassert wcss aon reset */ ++ ret = reset_control_deassert(wcss->wcss_aon_reset); ++ if (ret) { ++ dev_err(wcss->dev, "wcss_aon_reset failed\n"); ++ return ret; ++ } ++ mdelay(1); ++ } ++ ++ ret = ipq6018_clks_prepare_enable(wcss); ++ if (ret) { ++ dev_err(wcss->dev, "failed to enable clock\n"); ++ return ret; ++ } ++ } ++ ++ val = readl(wcss->rmb_base + SSCAON_CONFIG); ++ val |= BIT(0); ++ writel(val, wcss->rmb_base + SSCAON_CONFIG); ++ mdelay(1); ++ ++ /*set CFG[18:15]=1* and clear CFG[1]=0*/ ++ val = readl(wcss->rmb_base + SSCAON_CONFIG); ++ val &= ~(SSCAON_BUS_MUX_MASK | WCSS_HM_RET); ++ val |= SSCAON_BUS_EN; ++ writel(val, wcss->rmb_base + SSCAON_CONFIG); ++ mdelay(1); ++ + /* Assert resets, stop core */ + val = readl(wcss->reg_base + Q6SS_RESET_REG); + val |= Q6SS_CORE_ARES | Q6SS_BUS_ARES_ENABLE | Q6SS_STOP_CORE; +@@ -195,7 +268,19 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + udelay(1); + ++ if (desc == &wcss_ipq6018_res_init) { ++ /* 10 - Wait till BHS Reset is done */ ++ ret = readl_poll_timeout(wcss->reg_base + Q6SS_BHS_STATUS, ++ val, (val & BHS_EN_REST_ACK), 1000, ++ Q6SS_TIMEOUT_US * 50); ++ if (ret) { ++ dev_err(wcss->dev, "BHS_STATUS not ON (rc:%d) val:0x%X\n", ret, val); ++ return ret; ++ } ++ } ++ + /* Put LDO in bypass mode */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val |= Q6SS_LDO_BYP; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + +@@ -205,6 +290,7 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + + /* Deassert memory peripheral sleep and L2 memory standby */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val |= Q6SS_L2DATA_STBY_N | Q6SS_SLP_RET_N; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + +@@ -219,7 +305,10 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + * array to turn on. + */ + val |= readl(wcss->reg_base + Q6SS_MEM_PWR_CTL); +- udelay(1); ++ if (desc == &wcss_ipq6018_res_init) ++ mdelay(10); ++ else ++ udelay(1); + } + /* Remove word line clamp */ + val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); +@@ -227,6 +316,7 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + + /* Remove IO clamp */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val &= ~Q6SS_CLAMP_IO; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + +@@ -245,6 +335,16 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + val &= ~Q6SS_STOP_CORE; + writel(val, wcss->reg_base + Q6SS_RESET_REG); + ++ /* Wait for SSCAON_STATUS */ ++ val = readl(wcss->rmb_base + SSCAON_STATUS); ++ ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS, ++ val, (val & 0xffff) == 0x10, 1000, ++ Q6SS_TIMEOUT_US * 1000); ++ if (ret) { ++ dev_err(wcss->dev, " Boot Error, SSCAON=0x%08X\n", val); ++ return ret; ++ } ++ + return 0; + } + +@@ -376,7 +476,7 @@ static int q6v5_wcss_qcs404_power_on(struct q6v5_wcss *wcss) + /* Read CLKOFF bit to go low indicating CLK is enabled */ + ret = readl_poll_timeout(wcss->reg_base + Q6SS_XO_CBCR, + val, !(val & BIT(31)), 1, +- HALT_CHECK_MAX_LOOPS); ++ Q6SS_TIMEOUT_US); + if (ret) { + dev_err(wcss->dev, + "xo cbcr enabling timed out (rc:%d)\n", ret); +@@ -528,13 +628,18 @@ static void q6v5_wcss_halt_axi_port(struct q6v5_wcss *wcss, + unsigned long timeout; + unsigned int val; + int ret; ++ const struct wcss_data *desc = device_get_match_data(wcss->dev); + +- /* Check if we're already idle */ +- ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); +- if (!ret && val) +- return; ++ if (desc != &wcss_ipq6018_res_init) { ++ /* Check if we're already idle */ ++ ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); ++ if (!ret && val) ++ return; ++ } + + /* Assert halt request */ ++ regmap_read(halt_map, offset + AXI_HALTREQ_REG, &val); ++ val |= BIT(0); + regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1); + + /* Wait for halt */ +@@ -547,12 +652,14 @@ static void q6v5_wcss_halt_axi_port(struct q6v5_wcss *wcss, + msleep(1); + } + +- ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); +- if (ret || !val) +- dev_err(wcss->dev, "port failed halt\n"); ++ if (desc != &wcss_ipq6018_res_init) { ++ ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); ++ if (ret || !val) ++ dev_err(wcss->dev, "port failed halt\n"); + +- /* Clear halt request (port will remain halted until reset) */ +- regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); ++ /* Clear halt request (port will remain halted until reset) */ ++ regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); ++ } + } + + static int q6v5_qcs404_wcss_shutdown(struct q6v5_wcss *wcss) +@@ -621,6 +728,7 @@ static int q6v5_qcs404_wcss_shutdown(struct q6v5_wcss *wcss) + + static int q6v5_wcss_powerdown(struct q6v5_wcss *wcss) + { ++ const struct wcss_data *desc = device_get_match_data(wcss->dev); + int ret; + u32 val; + +@@ -638,21 +746,26 @@ static int q6v5_wcss_powerdown(struct q6v5_wcss *wcss) + writel(val, wcss->rmb_base + SSCAON_CONFIG); + + /* 4 - SSCAON_CONFIG 1 */ ++ val = readl(wcss->rmb_base + SSCAON_CONFIG); + val |= BIT(1); + writel(val, wcss->rmb_base + SSCAON_CONFIG); + + /* 5 - wait for SSCAON_STATUS */ + ret = readl_poll_timeout(wcss->rmb_base + SSCAON_STATUS, + val, (val & 0xffff) == 0x400, 1000, +- HALT_CHECK_MAX_LOOPS); ++ Q6SS_TIMEOUT_US * 10); + if (ret) { + dev_err(wcss->dev, + "can't get SSCAON_STATUS rc:%d)\n", ret); + return ret; + } + ++ mdelay(2); ++ + /* 6 - De-assert WCSS_AON reset */ + reset_control_assert(wcss->wcss_aon_reset); ++ if (desc == &wcss_ipq6018_res_init) ++ mdelay(1); + + /* 7 - Disable WCSSAON_CONFIG 13 */ + val = readl(wcss->rmb_base + SSCAON_CONFIG); +@@ -662,6 +775,13 @@ static int q6v5_wcss_powerdown(struct q6v5_wcss *wcss) + /* 8 - De-assert WCSS/Q6 HALTREQ */ + reset_control_assert(wcss->wcss_reset); + ++ if (desc == &wcss_ipq6018_res_init) { ++ /* Clear halt request (port will remain halted until reset) */ ++ regmap_read(wcss->halt_map, wcss->halt_wcss + AXI_HALTREQ_REG, &val); ++ val &= ~0x1; ++ regmap_write(wcss->halt_map, wcss->halt_wcss + AXI_HALTREQ_REG, val); ++ } ++ + return 0; + } + +@@ -670,6 +790,12 @@ static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) + int ret; + u32 val; + int i; ++ const struct wcss_data *desc = device_get_match_data(wcss->dev); ++ ++ if (desc == &wcss_ipq6018_res_init) { ++ /* To retain power domain after q6 powerdown */ ++ writel(0x1, wcss->reg_base + Q6SS_DBG_CFG); ++ } + + /* 1 - Halt Q6 bus interface */ + q6v5_wcss_halt_axi_port(wcss, wcss->halt_map, wcss->halt_q6); +@@ -685,14 +811,17 @@ static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + + /* 4 - Clamp WL */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val |= QDSS_BHS_ON; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + + /* 5 - Clear Erase standby */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val &= ~Q6SS_L2DATA_STBY_N; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + + /* 6 - Clear Sleep RTN */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val &= ~Q6SS_SLP_RET_N; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + +@@ -710,6 +839,7 @@ static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + + /* 9 - Turn off BHS */ ++ val = readl(wcss->reg_base + Q6SS_PWR_CTL_REG); + val &= ~Q6SS_BHS_ON; + writel(val, wcss->reg_base + Q6SS_PWR_CTL_REG); + udelay(1); +@@ -717,7 +847,7 @@ static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) + /* 10 - Wait till BHS Reset is done */ + ret = readl_poll_timeout(wcss->reg_base + Q6SS_BHS_STATUS, + val, !(val & BHS_EN_REST_ACK), 1000, +- HALT_CHECK_MAX_LOOPS); ++ Q6SS_TIMEOUT_US * 10); + if (ret) { + dev_err(wcss->dev, "BHS_STATUS not OFF (rc:%d)\n", ret); + return ret; +@@ -725,9 +855,23 @@ static int q6v5_q6_powerdown(struct q6v5_wcss *wcss) + + /* 11 - Assert WCSS reset */ + reset_control_assert(wcss->wcss_reset); ++ if (desc == &wcss_ipq6018_res_init) ++ mdelay(1); + + /* 12 - Assert Q6 reset */ + reset_control_assert(wcss->wcss_q6_reset); ++ if (desc == &wcss_ipq6018_res_init) { ++ mdelay(2); ++ ++ /* Clear halt request (port will remain halted until reset) */ ++ regmap_read(wcss->halt_map, wcss->halt_q6 + AXI_HALTREQ_REG, &val); ++ val &= ~0x1; ++ regmap_write(wcss->halt_map, wcss->halt_q6 + AXI_HALTREQ_REG, val); ++ mdelay(1); ++ ++ /* Disable clocks*/ ++ ipq6018_clks_prepare_disable(wcss); ++ } + + return 0; + } +@@ -966,6 +1110,57 @@ static int q6v5_alloc_memory_region(struct q6v5_wcss *wcss) + return 0; + } + ++static int ipq6018_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; ++ } ++ ++ wcss->gcc_sys_noc_wcss_ahb_clk = ++ devm_clk_get(wcss->dev, "gcc_sys_noc_wcss_ahb_clk"); ++ if (IS_ERR(wcss->gcc_sys_noc_wcss_ahb_clk)) { ++ ret = PTR_ERR(wcss->gcc_sys_noc_wcss_ahb_clk); ++ if (ret != -EPROBE_DEFER) ++ dev_err(wcss->dev, "Failed to get sys_noc_wcss_ahb clock\n"); ++ return ret; ++ } ++ ++ wcss->gcc_q6ss_atbm_clk = ++ devm_clk_get(wcss->dev, "gcc_q6ss_atbm_clk"); ++ if (IS_ERR(wcss->gcc_q6ss_atbm_clk)) { ++ ret = PTR_ERR(wcss->gcc_q6ss_atbm_clk); ++ if (ret != -EPROBE_DEFER) ++ dev_err(wcss->dev, "Failed to get q6ss_atbm clock\n"); ++ return ret; ++ } ++ ++ wcss->gcc_q6ss_pclkdbg_clk = ++ devm_clk_get(wcss->dev, "gcc_q6ss_pclkdbg_clk"); ++ if (IS_ERR(wcss->gcc_q6ss_pclkdbg_clk)) { ++ ret = PTR_ERR(wcss->gcc_q6ss_pclkdbg_clk); ++ if (ret != -EPROBE_DEFER) ++ dev_err(wcss->dev, "Failed to get q6ss_pclkdbg clock\n"); ++ return ret; ++ } ++ ++ wcss->gcc_q6_tsctr_1to2_clk = ++ devm_clk_get(wcss->dev, "gcc_q6_tsctr_1to2_clk"); ++ if (IS_ERR(wcss->gcc_q6_tsctr_1to2_clk)) { ++ ret = PTR_ERR(wcss->gcc_q6_tsctr_1to2_clk); ++ if (ret != -EPROBE_DEFER) ++ dev_err(wcss->dev, "Failed to get q6_tsctr_1to2 clock\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ + static int ipq8074_init_clock(struct q6v5_wcss *wcss) + { + int ret; +@@ -1174,7 +1369,7 @@ static int q6v5_wcss_remove(struct platform_device *pdev) + } + + static const struct wcss_data wcss_ipq6018_res_init = { +- .init_clock = ipq8074_init_clock, ++ .init_clock = ipq6018_init_clock, + .q6_firmware_name = "IPQ6018/q6_fw.mdt", + .m3_firmware_name = "IPQ6018/m3_fw.mdt", + .crash_reason_smem = WCSS_CRASH_REASON, +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1010-Simplify-ipq6018-rproc-Add-non-secure-Q6-bringup-seq.patch b/target/linux/ipq60xx/patches-5.15/1010-Simplify-ipq6018-rproc-Add-non-secure-Q6-bringup-seq.patch new file mode 100644 index 000000000..a62f30b9c --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1010-Simplify-ipq6018-rproc-Add-non-secure-Q6-bringup-seq.patch @@ -0,0 +1,60 @@ +From 0fcb001e301b7dec639f948de49f69666fe91669 Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +Date: Fri, 2 Sep 2022 18:31:11 -0500 +Subject: [PATCH 1010/1011] Simplify "ipq6018: rproc: Add non secure Q6 bringup + sequence" + +--- + drivers/remoteproc/qcom_q6v5_wcss.c | 25 +++++++++---------------- + 1 file changed, 9 insertions(+), 16 deletions(-) + +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index d07b710c42db..694d97a0d807 100644 +--- a/drivers/remoteproc/qcom_q6v5_wcss.c ++++ b/drivers/remoteproc/qcom_q6v5_wcss.c +@@ -628,18 +628,13 @@ static void q6v5_wcss_halt_axi_port(struct q6v5_wcss *wcss, + unsigned long timeout; + unsigned int val; + int ret; +- const struct wcss_data *desc = device_get_match_data(wcss->dev); + +- if (desc != &wcss_ipq6018_res_init) { +- /* Check if we're already idle */ +- ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); +- if (!ret && val) +- return; +- } ++ /* Check if we're already idle */ ++ ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); ++ if (!ret && val) ++ return; + + /* Assert halt request */ +- regmap_read(halt_map, offset + AXI_HALTREQ_REG, &val); +- val |= BIT(0); + regmap_write(halt_map, offset + AXI_HALTREQ_REG, 1); + + /* Wait for halt */ +@@ -652,14 +647,12 @@ static void q6v5_wcss_halt_axi_port(struct q6v5_wcss *wcss, + msleep(1); + } + +- if (desc != &wcss_ipq6018_res_init) { +- ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); +- if (ret || !val) +- dev_err(wcss->dev, "port failed halt\n"); ++ ret = regmap_read(halt_map, offset + AXI_IDLE_REG, &val); ++ if (ret || !val) ++ dev_err(wcss->dev, "port failed halt\n"); + +- /* Clear halt request (port will remain halted until reset) */ +- regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); +- } ++ /* Clear halt request (port will remain halted until reset) */ ++ regmap_write(halt_map, offset + AXI_HALTREQ_REG, 0); + } + + static int q6v5_qcs404_wcss_shutdown(struct q6v5_wcss *wcss) +-- +2.37.1 + diff --git a/target/linux/ipq60xx/patches-5.15/1011-HACK-q6v5_wcss-Sprinkle-pointless-debug-print-statem.patch b/target/linux/ipq60xx/patches-5.15/1011-HACK-q6v5_wcss-Sprinkle-pointless-debug-print-statem.patch new file mode 100644 index 000000000..60aab6e38 --- /dev/null +++ b/target/linux/ipq60xx/patches-5.15/1011-HACK-q6v5_wcss-Sprinkle-pointless-debug-print-statem.patch @@ -0,0 +1,189 @@ +From d5701fe34dbf374ce3518a38695f713d02268232 Mon Sep 17 00:00:00 2001 +From: Alexandru Gagniuc +Date: Mon, 29 Aug 2022 21:45:09 -0500 +Subject: [PATCH 1011/1011] [HACK] q6v5_wcss: Sprinkle pointless debug print + statements + +--- + drivers/remoteproc/qcom_common.c | 4 ++++ + drivers/remoteproc/qcom_q6v5_wcss.c | 25 ++++++++++++++++++++++++- + drivers/remoteproc/remoteproc_core.c | 8 ++++++++ + 3 files changed, 36 insertions(+), 1 deletion(-) + +diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c +index 4b91e3c9eafa..fdab60430932 100644 +--- a/drivers/remoteproc/qcom_common.c ++++ b/drivers/remoteproc/qcom_common.c +@@ -201,7 +201,9 @@ static void glink_subdev_unprepare(struct rproc_subdev *subdev) + { + struct qcom_rproc_glink *glink = to_glink_subdev(subdev); + ++ dev_err(glink->dev, "%s: presub \n", __func__); + qcom_glink_ssr_notify(glink->ssr_name); ++ dev_err(glink->dev, "%s: done \n", __func__); + } + + /** +@@ -458,8 +460,10 @@ static void ssr_notify_unprepare(struct rproc_subdev *subdev) + .crashed = false, + }; + ++ pr_err("%s: notif \n", __func__); + srcu_notifier_call_chain(&ssr->info->notifier_list, + QCOM_SSR_AFTER_SHUTDOWN, &data); ++ pr_err("%s: done \n", __func__); + } + + /** +diff --git a/drivers/remoteproc/qcom_q6v5_wcss.c b/drivers/remoteproc/qcom_q6v5_wcss.c +index 694d97a0d807..ef50ba6b068b 100644 +--- a/drivers/remoteproc/qcom_q6v5_wcss.c ++++ b/drivers/remoteproc/qcom_q6v5_wcss.c +@@ -178,6 +178,9 @@ static int ipq6018_clks_prepare_enable(struct q6v5_wcss *wcss) + { + int ret; + ++ dev_err(wcss->dev, "%s\n", __func__); ++ ++ + ret = clk_prepare_enable(wcss->gcc_sys_noc_wcss_ahb_clk); + if (ret) + return ret; +@@ -212,6 +215,8 @@ static int q6v5_wcss_reset(struct q6v5_wcss *wcss) + u32 val; + int i; + ++ dev_err(wcss->dev, "%s\n", __func__); ++ + desc = device_get_match_data(wcss->dev); + if (desc == &wcss_ipq6018_res_init) { + if (desc->aon_reset_required) { +@@ -353,6 +358,8 @@ static int q6v5_wcss_start(struct rproc *rproc) + struct q6v5_wcss *wcss = rproc->priv; + int ret; + ++ dev_err(wcss->dev, "%s\n", __func__); ++ + ret = clk_prepare_enable(wcss->prng_clk); + if (ret) { + dev_err(wcss->dev, "prng clock enable failed\n"); +@@ -367,6 +374,8 @@ static int q6v5_wcss_start(struct rproc *rproc) + dev_err(wcss->dev, "wcss_reset failed\n"); + return ret; + } ++ ++ dev_err(wcss->dev, "%s: I'm scared, therefore I goto\n", __func__); + goto wait_for_reset; + } + +@@ -874,6 +883,8 @@ static int q6v5_wcss_stop(struct rproc *rproc) + struct q6v5_wcss *wcss = rproc->priv; + int ret; + ++ dev_err(wcss->dev, "%s\n", __func__); ++ + if (wcss->need_mem_protection) { + ret = qcom_scm_pas_shutdown(WCNSS_PAS_ID); + if (ret) { +@@ -885,6 +896,7 @@ static int q6v5_wcss_stop(struct rproc *rproc) + + /* WCSS powerdown */ + if (wcss->requires_force_stop) { ++ dev_err(wcss->dev, "%s: Forcetopsky \n", __func__); + ret = qcom_q6v5_request_stop(&wcss->q6v5, NULL); + if (ret == -ETIMEDOUT) { + dev_err(wcss->dev, "timed out on wait\n"); +@@ -897,10 +909,12 @@ static int q6v5_wcss_stop(struct rproc *rproc) + if (ret) + return ret; + } else { ++ dev_err(wcss->dev, "%s: Preodonsky \n", __func__); + ret = q6v5_wcss_powerdown(wcss); + if (ret) + return ret; + ++ dev_err(wcss->dev, "%s: Podonsky \n", __func__); + /* Q6 Power down */ + ret = q6v5_q6_powerdown(wcss); + if (ret) +@@ -908,9 +922,14 @@ static int q6v5_wcss_stop(struct rproc *rproc) + } + + pas_done: ++ dev_err(wcss->dev, "%s: pasdonsky \n", __func__); + clk_disable_unprepare(wcss->prng_clk); + qcom_q6v5_unprepare(&wcss->q6v5); + ++ dev_err(wcss->dev, "%s: outsky \n", __func__); ++ WARN_ON(1); ++ mdelay(420); ++ + return 0; + } + +@@ -1106,6 +1125,8 @@ static int q6v5_alloc_memory_region(struct q6v5_wcss *wcss) + static int ipq6018_init_clock(struct q6v5_wcss *wcss) + { + int ret; ++ dev_err(wcss->dev, "%s\n", __func__); ++ + + wcss->prng_clk = devm_clk_get(wcss->dev, "prng"); + if (IS_ERR(wcss->prng_clk)) { +@@ -1276,6 +1297,8 @@ static int q6v5_wcss_probe(struct platform_device *pdev) + struct rproc *rproc; + int ret; + ++ dev_err(&pdev->dev, "%s\n", __func__); ++ + desc = device_get_match_data(&pdev->dev); + if (!desc) + return -EINVAL; +@@ -1372,7 +1395,7 @@ static const struct wcss_data wcss_ipq6018_res_init = { + .ssr_name = "q6wcss", + .ops = &q6v5_wcss_ipq8074_ops, + .requires_force_stop = false, +- .need_mem_protection = true, ++ .need_mem_protection = false, + .need_auto_boot = false, + }; + +diff --git a/drivers/remoteproc/remoteproc_core.c b/drivers/remoteproc/remoteproc_core.c +index 775df165eb45..6c6ab68ca244 100644 +--- a/drivers/remoteproc/remoteproc_core.c ++++ b/drivers/remoteproc/remoteproc_core.c +@@ -1822,9 +1822,13 @@ static int rproc_stop(struct rproc *rproc, bool crashed) + if (!rproc->ops->stop) + return -EINVAL; + ++ dev_err(dev, "%s: presub \n", __func__); ++ + /* Stop any subdevices for the remote processor */ + rproc_stop_subdevices(rproc, crashed); + ++ dev_err(dev, "%s: postsub \n", __func__); ++ + /* the installed resource table is no longer accessible */ + ret = rproc_reset_rsc_table_on_stop(rproc); + if (ret) { +@@ -1832,6 +1836,8 @@ static int rproc_stop(struct rproc *rproc, bool crashed) + return ret; + } + ++ dev_err(dev, "%s: really-stop \n", __func__); ++ + + /* power off the remote processor */ + ret = rproc->ops->stop(rproc); +@@ -1840,6 +1846,8 @@ static int rproc_stop(struct rproc *rproc, bool crashed) + return ret; + } + ++ dev_err(dev, "%s: no really, you're stopped \n", __func__); ++ + rproc_unprepare_subdevices(rproc); + + rproc->state = RPROC_OFFLINE; +-- +2.37.1 +