regulator: add snapshot of cpr3-regulator and dependent drivers
Add a snapshot of the cpr3-regulator family of drivers as well as
the other drivers that CPR depends upon.
This snapshot is taken as of msm-4.4
commit d24550bbf50f ("Merge "ARM: dts: msm: Add slimbus slave
device for wcn3990 on sdm630"").
Change-Id: Ic7f81d77a8642d8581242d2a7a46c26ac3eb81a4
Signed-off-by: David Collins <collinsd@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/power/apm.txt b/Documentation/devicetree/bindings/power/apm.txt
new file mode 100644
index 0000000..fa03edf
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/apm.txt
@@ -0,0 +1,73 @@
+MSM Array Power Mux (msm-apm)
+
+In some MSM designs, the CPU and caches contain logic which can be powered by
+multiple rails. The APM controller exists to enable the selection of the power
+rail supplying these arrays. Since a given supply may drop below the array
+SRAM minimum operating voltage, the APM controller can be used to request a
+switch to a power supply that will guarantee logic state retention.
+
+Required properties
+- compatible: "qcom,msm-apm", "qcom,msm8996pro-apm", "qcom,msm8953-apm"
+- reg: Specifies physical base address and size of memory mapped regions
+ containing the APM controller, APCS CSR, APC PLL controller, and
+ SPM event registers.
+- reg-names: List of strings identifying the reg property entries. This list must
+ contain: "pm-apcc-glb", "apcs-csr", "apc0-pll-ctl", "apc1-pll-ctl",
+ "apc0-cpu0-spm", "apc0-cpu1-spm", "apc1-cpu0-spm", "apc1-cpu1-spm",
+ "apc0-l2-spm", and "apc1-l2-spm".
+
+Optional properties
+- qcom,clock-source-override: Specify this property to request a switch of the APC0 and APC1
+ clock sources to GPLL0 before the APM switch begins and to
+ switch back to the original clock source after the APM switch
+ completes.
+- qcom,apm-post-halt-delay: The APM controller post halt delay counter value that SW needs
+ to program one time before starting the APM HW controller for
+ msm8953 target.
+- qcom,apm-halt-clk-delay: The APM controller halt clock delay counter value that SW
+ needs to program one time before starting the APM HW controller
+ for msm8953 target.
+- qcom,apm-resume-clk-delay: The APM controller resume clock delay counter value that SW
+ needs to program one time before starting the APM HW controller
+ for msm8953 target.
+- qcom,apm-sel-switch-delay: The APM controller switch selection delay counter value that SW
+ needs to program one time before starting the APM HW controller
+ for msm8953 target.
+
+MSM APM Users
+
+Required properties:
+- qcom,apm-ctrl: phandle of APM controller device node
+
+Example:
+ apc_apm: apm@099e0000 {
+ compatible = "qcom,msm-apm";
+ reg = <0x099e0000 0x1000>,
+ <0x09820000 0x10000>,
+ <0x06400050 0x8>,
+ <0x06480050 0x8>,
+ <0x09981068 0x8>,
+ <0x09991068 0x8>,
+ <0x099b1068 0x8>,
+ <0x099c1068 0x8>,
+ <0x099a1068 0x8>,
+ <0x099d1068 0x8>;
+ reg-names = "pm-apcc-glb",
+ "apcs-csr",
+ "apc0-pll-ctl",
+ "apc1-pll-ctl",
+ "apc0-cpu0-spm",
+ "apc0-cpu1-spm",
+ "apc1-cpu0-spm",
+ "apc1-cpu1-spm",
+ "apc0-l2-spm",
+ "apc1-l2-spm";
+ qcom,apm-post-halt-delay = <0x2>;
+ qcom,apm-halt-clk-delay = <0x11>;
+ qcom,apm-resume-clk-delay = <0x10>;
+ qcom,apm-sel-switch-delay = <0x01>;
+
+ foo_user {
+ ...
+ qcom,apm-ctrl = <&apc_apm>;
+ };
diff --git a/Documentation/devicetree/bindings/regulator/cpr3-hmss-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr3-hmss-regulator.txt
new file mode 100644
index 0000000..2af3cbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cpr3-hmss-regulator.txt
@@ -0,0 +1,556 @@
+Qualcomm Technologies, Inc. CPR3 Regulator - HMSS Specific Bindings
+
+HMSS CPR3 controllers each support two CPR threads that monitor the voltage of
+a pair of application processor (HMSS) clusters that are powered by a shared
+regulator supply. These controllers have a hardware aggregator to combine the
+UP/DOWN requests from each CPR thread into a single unified request. They also
+have a hardware channel to use these requests to directly change the supply
+voltage at the PMIC via the SPM without software intervention.
+
+HMSS CPR3 controllers also have to take into account the state of the memory
+array power mux (APM) when scaling voltage to ensure that memory always receives
+a sufficiently high voltage.
+
+Both CPR open-loop voltages and CPR target quotients are stored in hardware
+fuses for HMSS CPR3 controllers.
+
+This document describes the HMSS specific CPR3 bindings.
+
+=======================
+Required Node Structure
+=======================
+
+CPR3 regulators must be described in three levels of devices nodes. The first
+level describes the CPR3 controller. The second level describes one or more
+hardware threads managed by the controller. The third level describes one or
+more logical regulators handled by each CPR thread.
+
+All platform independent cpr3-regulator binding guidelines defined in
+cpr3-regulator.txt also apply to cpr3-hmss-regulator devices.
+
+====================================
+First Level Nodes - CPR3 Controllers
+====================================
+
+HMSS specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be one of the following:
+ "qcom,cpr3-msm8996-v1-hmss-regulator",
+ "qcom,cpr3-msm8996-v2-hmss-regulator",
+ "qcom,cpr3-msm8996-v3-hmss-regulator",
+ "qcom,cpr3-msm8996-hmss-regulator",
+ "qcom,cpr3-msm8996pro-hmss-regulator".
+ If the SoC revision is not specified, then it is assumed to
+ be the most recent revision of MSM8996, i.e. v3.
+
+- interrupts
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: CPR interrupt specifier and a hardware closed-loop ceiling
+ interrupt specifier.
+
+- interrupt-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Interrupt names. This list must match up 1-to-1 with the
+ interrupts specified in the 'interrupts' property. "cpr"
+ and "ceiling" must be specified.
+
+- qcom,apm-ctrl
+ Usage: required on systems that need APM management
+ Value type: <phandle>
+ Definition: phandle of memory array power mux (APM) controller device
+ node for the APM that is used by the HMSS VDD supply
+
+- qcom,apm-threshold-voltage
+ Usage: required if qcom,apm-ctrl is specified
+ Value type: <u32>
+ Definition: Specifies the APM threshold voltage in microvolts. If the
+ vdd-supply voltage is greater than or equal to this level,
+ then the APM is switched to use the vdd-supply. If the
+ vdd-supply voltage is below this level, then the APM is
+ switched to use the system-supply.
+
+- qcom,apm-hysteresis-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the voltage delta in microvolts between the APM
+ threshold voltage and the highest corner open-loop voltage
+ which may be used as the ceiling for the corner. If this
+ property is not specified, then a value of 0 is assumed.
+
+- qcom,system-supply-max-voltage
+ Usage: required if qcom,vdd-threadN-ldo-supply is specified for any
+ CPR3 regulator managed by any CPR3 thread of this controller.
+ Value type: <u32>
+ Definition: Maximum voltage setpoint for the system-supply regulator.
+
+- qcom,mem-acc-supply-threshold-voltage
+ Usage: required if mem-acc-supply or mem-acc-threadN-supply is
+ specified and the CPR3 controller or any of the CPR3 regulators
+ it controls needs to manage mem-acc settings.
+ Value type: <u32>
+ Definition: Specifies the mem-acc-supply threshold voltage in microvolts.
+ If the vdd-supply voltage is greater than or equal to this
+ level, then the mem-acc-supply regulator is switched to the
+ high voltage corner setting. Conversely, if the vdd-supply
+ voltage is below this level, then the mem-acc-supply regulator
+ is switched to the low voltage corner setting.
+
+- qcom,mem-acc-supply-corner-map
+ Usage: required if mem-acc-supply or mem-acc-threadN-supply is
+ specified and the CPR3 controller or any of the CPR3 regulators
+ it controls needs to manage mem-acc settings.
+ Value type: <prop-encoded-array>
+ Definition: A tuple containing two integers which defines the mem-acc-supply
+ corner to use for low and high vdd-supply voltages, respectively.
+
+- qcom,cpr-up-down-delay-time
+ Usage: required
+ Value type: <u32>
+ Definition: The time to delay in nanoseconds between consecutive CPR
+ measurements when the last measurement recommended
+ increasing or decreasing the vdd-supply voltage.
+
+- qcom,cpr-hw-closed-loop
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the HMSS CPR3 controller
+ should operate in hardware closed-loop mode as opposed to
+ software closed-loop mode.
+
+- vdd-limit-supply
+ Usage: required
+ Value type: <phandle>
+ Definition: phandle of the VDD supply limit regulator which controls the
+ CPR ceiling and floor voltages when operating in hardware
+ closed-loop mode.
+
+- qcom,cpr-clock-throttling
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the power domains for which CPR processor clock
+ throttling should be enabled. This feature reduces the
+ processor's clock frequency when it is resuming from a low
+ power mode until CPR is able to raise the supply voltage to
+ a final settled value. The following bits may be set:
+ BIT(0) - Power cluster L2 cache
+ BIT(1) - Power cluster core 1
+ BIT(2) - Power cluster core 0
+ BIT(5) - Performance cluster L2 cache
+ BIT(6) - Performance cluster core 1
+ BIT(7) - Performance cluster core 0
+
+- mem-acc-threadN-supply
+ Usage: optional
+ Value type: <phandle>
+ Definition: phandle of the regulator device which manages mem-acc
+ configuration for the clusters per CPR thread. 'N' must
+ match with the hardware thread ID of the thread it controls.
+
+- vdd-threadN-ldo-supply
+ Usage: optional
+ Value type: <phandle>
+ Definition: phandle of the regulator device which manages LDO and BHS
+ modes for the clusters per CPR thread. 'N' must match with
+ the hardware thread ID of the thread it controls.
+
+- vdd-threadN-ldo-ret-supply
+ Usage: required if vdd-threadN-ldo-supply is specified for
+ this CPR thread.
+ Value type: <phandle>
+ Definition: phandle of the regulator device which manages LDO retention
+ modes for the clusters per CPR thread. 'N' must match with
+ the hardware thread ID of the thread it controls.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+HMSS specific properties:
+N/A
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+HMSS specific properties:
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. This value must be 5
+ for HMSS. These fuse corners are: MinSVS, LowSVS, SVS,
+ Nominal, and Turbo. Note that a specific fused target
+ quotient is available for the LowSVS corner but a fused
+ open-loop voltage is not available. The LowSVS open-loop
+ voltage is calculated using linear interpolation between
+ the MinSVS and SVS values.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. For HMSS the maximum
+ supported value is 16. The first 8 fuse combos correspond
+ to speed bin fuse value 0 along with CPR revision fuse
+ values 0 to 7. The last 8 fuse combos correspond to speed
+ bin fuse value 1 along with CPR revision fuse values 0 to 7.
+
+- qcom,cpr-speed-bins
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the number of speed bins being supported by the
+ device. This value is utilized by several other properties.
+ Supported values are 1 up to the maximum possible for a
+ given regulator type. For HMSS the maximum supported value
+ is 2.
+
+- qcom,is-cbf-regulator
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the CPR3 regulator
+ corresponds to the CBF clock domain. Special fuses are
+ defined for the CBF CPR3 regulator on MSM8996-Pro chips.
+
+- qcom,ldo-min-headroom-voltage
+ Usage: required if vdd-threadN-ldo-supply is specified for the
+ CPR3 thread containing this CPR3 regulator and this CPR3
+ regulator needs to manage the cluster LDO state.
+ Value type: <u32>
+ Definition: Voltage in microvolts required between the VDD_APCC voltage
+ and the LDO output in order for the LDO to be operational.
+
+- qcom,ldo-max-headroom-voltage
+ Usage: required if vdd-threadN-ldo-supply is specified for the
+ CPR3 thread containing this CPR3 regulator and this CPR3
+ regulator needs to manage the cluster LDO state.
+ Value type: <u32>
+ Definition: Maximum voltage difference in microvolts between the vdd-supply
+ voltage and the LDO output voltage in order for active LDO mode
+ to be operational.
+
+- qcom,ldo-adjust-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Voltage in microvolts used to offset margins between PMIC
+ output and CPU.
+
+- qcom,ldo-max-voltage
+ Usage: required if qcom,ldo-min-headroom-voltage is specified for this
+ CPR3 regulator.
+ Value type: <u32>
+ Definition: Voltage in microvolts which represents the maximum
+ physically supported voltage output of the LDO hardware.
+
+- qcom,uses-mem-acc
+ Usage: required if mem-acc-threadN-supply is specified for the
+ CPR3 thread containing this CPR3 regulator and this CPR3
+ regulator needs to manage mem-acc settings.
+ Value type: <empty>
+ Definition: Boolean flag which indicates that this CPR3 regulator must
+ manage mem-acc.
+
+- qcom,ldo-disable
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that LDO mode usage is
+ disallowed. If this flag is present, then the
+ vdd-threadN-ldo-supply mode will not be modified.
+
+- qcom,allow-quotient-interpolation
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that it is acceptable to use
+ interpolated CPR target quotient values. These values are
+ interpolated between the target quotient Fmax fuse values.
+
+- qcom,cpr-pd-bypass-mask
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the power domains associated with this CPR3
+ regulator. The following bits may be set:
+ BIT(0) - Power cluster L2 cache
+ BIT(1) - Power cluster core 1
+ BIT(2) - Power cluster core 0
+ BIT(3) - CBF
+ BIT(4) - L3 cache
+ BIT(5) - Performance cluster L2 cache
+ BIT(6) - Performance cluster core 1
+ BIT(7) - Performance cluster core 0
+
+- qcom,cpr-dynamic-floor-corner
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines for each fuse combination
+ the CPR corner whose closed-loop voltage should be used as
+ a CPR floor voltage whenever the power domains for this CPR3
+ regulator are bypassed. Supported values are 0 and 1 to N.
+ A value of 0 means that no dynamic floor is needed. N is
+ the number of corners defined for this fuse combination in
+ the qcom,cpr-corners property.
+
+ The list must contain qcom,cpr-fuse-combos number of
+ elements in which case the elements are matched to fuse
+ combinations 1-to-1 or qcom,cpr-speed-bins number of
+ elements in which case the elements are matched to
+ speed bins 1-to-1 or exactly 1 element which is used
+ regardless of the fuse combination and speed bin found
+ on a given chip.
+
+=======
+Example
+=======
+
+apcc_cpr: cpr3-ctrl@99e8000 {
+ compatible = "qcom,cpr3-msm8996-hmss-regulator";
+ reg = <0x099e8000 0x4000>, <0x00074000 0x1000>;
+ reg-names = "cpr_ctrl", "fuse_base";
+ clocks = <&clock_gcc clk_gcc_hmss_rbcpr_clk>;
+ clock-names = "core_clk";
+ interrupts = <0 48 0>, <0 47 0>;
+ interrupt-names = "cpr", "ceiling";
+ qcom,cpr-interrupt-affinity = <&CPU0 &CPU1>;
+ qcom,cpr-ctrl-name = "apcc";
+
+ qcom,cpr-sensor-time = <1000>;
+ qcom,cpr-loop-time = <5000000>;
+ qcom,cpr-idle-cycles = <15>;
+ qcom,cpr-up-down-delay-time = <3000>;
+ qcom,cpr-step-quot-init-min = <13>;
+ qcom,cpr-step-quot-init-max = <13>;
+ qcom,cpr-count-mode = <2>;
+
+ qcom,apm-ctrl = <&apc_apm>;
+ qcom,apm-threshold-voltage = <850000>;
+ qcom,apm-hysteresis-voltage = <5000>;
+ qcom,system-supply-max-voltage = <1015000>;
+ qcom,mem-acc-supply-threshold-voltage = <700000>;
+ qcom,mem-acc-supply-corner-map = <1 2>;
+
+ vdd-supply = <&pm8994_s11>;
+ qcom,voltage-step = <5000>;
+ vdd-limit-supply = <&pm8994_s11_limit>;
+ mem-acc-thread0-supply = <&apc0_pwrcl_mem_acc_vreg>;
+ mem-acc-thread1-supply = <&apc1_perfcl_mem_acc_vreg>;
+ mem-acc-supply = <&apcc_l3_mem_acc_vreg>;
+ vdd-thread0-ldo-supply = <&kryo0_vreg>;
+ vdd-thread1-ldo-supply = <&kryo1_vreg>;
+ vdd-thread0-ldo-ret-supply = <&kryo0_retention_vreg>;
+ vdd-thread1-ldo-ret-supply = <&kryo1_retention_vreg>;
+
+ qcom,cpr-enable;
+ qcom,cpr-hw-closed-loop;
+ qcom,cpr-clock-throttling = <0x20>;
+
+ qcom,cpr-aging-ref-voltage = <905000>;
+
+ thread@0 {
+ qcom,cpr-thread-id = <0>;
+ qcom,cpr-consecutive-up = <0>;
+ qcom,cpr-consecutive-down = <2>;
+ qcom,cpr-up-threshold = <0>;
+ qcom,cpr-down-threshold = <2>;
+
+ apc0_pwrcl_vreg: regulator-pwrcl {
+ regulator-name = "apc0_pwrcl_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <19>;
+
+ qcom,cpr-pd-bypass-mask = <0x07>;
+ qcom,cpr-fuse-corners = <5>;
+ qcom,cpr-fuse-combos = <1>;
+ qcom,cpr-corners = <19>;
+
+ qcom,ldo-min-headroom-voltage = <150000>;
+ qcom,ldo-max-headroom-voltage = <470000>;
+ qcom,ldo-max-voltage = <805000>;
+ qcom,uses-mem-acc;
+
+ qcom,cpr-corner-fmax-map = <1 2 6 11 19>;
+
+ qcom,cpr-voltage-ceiling =
+ <670000 670000 745000 745000 745000
+ 745000 905000 905000 905000 905000
+ 905000 1015000 1015000 1015000 1015000
+ 1015000 1015000 1015000 1015000>;
+ qcom,cpr-voltage-floor =
+ <520000 550000 555000 565000 585000
+ 615000 635000 655000 690000 720000
+ 740000 750000 760000 770000 780000
+ 790000 815000 840000 850000>;
+ qcom,corner-frequencies =
+ <192000000 268800000 307200000
+ 345600000 403200000 480000000
+ 576000000 633600000 729600000
+ 806400000 883200000 960000000
+ 1017600000 1113600000 1190400000
+ 1267200000 1344000000 1420800000
+ 1459200000>;
+
+ qcom,cpr-ro-scaling-factor =
+ < 0 0 0 0 2222 2275 2506 2491
+ 2649 2640 2886 2866 0 0 0 0>,
+ < 0 0 0 0 2222 2275 2506 2491
+ 2649 2640 2886 2866 0 0 0 0>,
+ < 0 0 0 0 2222 2275 2506 2491
+ 2649 2640 2886 2866 0 0 0 0>,
+ < 0 0 0 0 2147 2226 2310 2312
+ 2450 2447 2603 2600 0 0 0 0>,
+ < 0 0 0 0 1989 2079 2066 2083
+ 2193 2201 2283 2296 0 0 0 0>;
+
+ qcom,cpr-open-loop-voltage-fuse-adjustment =
+ <0 0 0 0 0>;
+ qcom,cpr-closed-loop-voltage-fuse-adjustment =
+ <0 0 0 0 0>;
+
+ qcom,allow-voltage-interpolation;
+ qcom,allow-quotient-interpolation;
+ qcom,cpr-scaled-open-loop-voltage-as-ceiling;
+
+ qcom,cpr-aging-max-voltage-adjustment = <25000>;
+ qcom,cpr-aging-ref-corner = <11>;
+ qcom,cpr-aging-ro-scaling-factor = <3200>;
+ qcom,cpr-aging-derate =
+ <1000 1000 1000 1000 1000 1000 1000 1000
+ 1000 1000 1000 1000 1000 1000 1000 1000
+ 1000 1000 1000>;
+ qcom,allow-aging-voltage-adjustment = <1>;
+ };
+
+ apc0_cbf_vreg: regulator-cbf {
+ regulator-name = "apc0_cbf_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <10>;
+ qcom,is-cbf-regulator;
+
+ qcom,cpr-pd-bypass-mask = <0x18>;
+ qcom,cpr-fuse-corners = <5>;
+ qcom,cpr-fuse-combos = <1>;
+ qcom,cpr-corners = <10>;
+
+ qcom,cpr-corner-fmax-map = <1 2 5 9 10>;
+
+ qcom,cpr-voltage-ceiling =
+ <605000 670000 745000 745000 745000
+ 905000 905000 905000 905000 1015000>;
+ qcom,cpr-voltage-floor =
+ <520000 545000 565000 595000 635000
+ 660000 690000 730000 750000 850000>;
+
+ qcom,corner-frequencies =
+ <150000000 307200000 384000000
+ 499200000 595200000 691200000
+ 787200000 883200000 960000000
+ 1036800000>;
+
+ qcom,cpr-ro-scaling-factor =
+ < 0 0 0 0 2222 2275 2506 2491
+ 2649 2640 2886 2866 0 0 0 0>,
+ < 0 0 0 0 2222 2275 2506 2491
+ 2649 2640 2886 2866 0 0 0 0>,
+ < 0 0 0 0 2222 2275 2506 2491
+ 2649 2640 2886 2866 0 0 0 0>,
+ < 0 0 0 0 2147 2226 2310 2312
+ 2450 2447 2603 2600 0 0 0 0>,
+ < 0 0 0 0 1989 2079 2066 2083
+ 2193 2201 2283 2296 0 0 0 0>;
+
+ qcom,cpr-open-loop-voltage-fuse-adjustment =
+ <0 0 0 0 0>;
+ qcom,cpr-closed-loop-voltage-fuse-adjustment =
+ <0 0 0 0 0>;
+
+ qcom,allow-voltage-interpolation;
+ qcom,allow-quotient-interpolation;
+ qcom,cpr-scaled-open-loop-voltage-as-ceiling;
+
+ qcom,cpr-aging-max-voltage-adjustment = <25000>;
+ qcom,cpr-aging-ref-corner = <9>;
+ qcom,cpr-aging-ro-scaling-factor = <3200>;
+ qcom,cpr-aging-derate =
+ <1000 1000 1000 1000 1000 1000 1000 1000
+ 1000 1000>;
+ qcom,allow-aging-voltage-adjustment = <1>;
+ };
+ };
+
+ thread@1 {
+ qcom,cpr-thread-id = <1>;
+ qcom,cpr-consecutive-up = <0>;
+ qcom,cpr-consecutive-down = <2>;
+ qcom,cpr-up-threshold = <0>;
+ qcom,cpr-down-threshold = <2>;
+
+ apc1_vreg: regulator {
+ regulator-name = "apc1_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <18>;
+
+ qcom,cpr-pd-bypass-mask = <0xe0>;
+ qcom,cpr-fuse-corners = <5>;
+ qcom,cpr-fuse-combos = <1>;
+ qcom,cpr-corners = <18>;
+
+ qcom,ldo-min-headroom-voltage = <150000>;
+ qcom,ldo-max-headroom-voltage = <470000>;
+ qcom,ldo-max-voltage = <805000>;
+ qcom,uses-mem-acc;
+
+ qcom,cpr-corner-fmax-map = <1 3 5 11 18>;
+
+ qcom,cpr-voltage-ceiling =
+ <670000 670000 670000 745000 745000
+ 905000 905000 905000 905000 905000
+ 905000 1015000 1015000 1015000 1015000
+ 1015000 1015000 1015000>;
+ qcom,cpr-voltage-floor =
+ <520000 530000 545000 590000 620000
+ 635000 660000 685000 700000 730000
+ 740000 750000 765000 790000 805000
+ 815000 830000 850000>;
+
+ qcom,corner-frequencies =
+ <307200000 345600000 403200000
+ 480000000 576000000 633600000
+ 729600000 806400000 883200000
+ 960000000 1017600000 1113600000
+ 1190400000 1267200000 1344000000
+ 1420800000 1497600000 1593600000>;
+
+ qcom,cpr-ro-scaling-factor =
+ < 0 0 0 0 2212 2273 2517 2506
+ 2663 2650 2908 2891 0 0 0 0>,
+ < 0 0 0 0 2212 2273 2517 2506
+ 2663 2650 2908 2891 0 0 0 0>,
+ < 0 0 0 0 2212 2273 2517 2506
+ 2663 2650 2908 2891 0 0 0 0>,
+ < 0 0 0 0 2152 2237 2321 2337
+ 2475 2469 2636 2612 0 0 0 0>,
+ < 0 0 0 0 2001 2102 2092 2090
+ 2203 2210 2297 2297 0 0 0 0>;
+
+ qcom,cpr-open-loop-voltage-fuse-adjustment =
+ <0 0 0 5000 0>;
+ qcom,cpr-closed-loop-voltage-fuse-adjustment =
+ <0 0 0 20000 0>;
+
+ qcom,allow-voltage-interpolation;
+ qcom,allow-quotient-interpolation;
+ qcom,cpr-scaled-open-loop-voltage-as-ceiling;
+
+ qcom,cpr-aging-max-voltage-adjustment = <25000>;
+ qcom,cpr-aging-ref-corner = <11>;
+ qcom,cpr-aging-ro-scaling-factor = <3200>;
+ qcom,cpr-aging-derate =
+ <1000 1000 1000 1000 1000 1000 1000 1000
+ 1000 1000 1000 1000 1000 1000 1000 1000
+ 1000 1000>;
+ qcom,allow-aging-voltage-adjustment = <1>;
+ };
+ };
+};
diff --git a/Documentation/devicetree/bindings/regulator/cpr3-mmss-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr3-mmss-regulator.txt
new file mode 100644
index 0000000..baa0cd2
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cpr3-mmss-regulator.txt
@@ -0,0 +1,345 @@
+Qualcomm Technologies, Inc. CPR3 Regulator - MMSS Specific Bindings
+
+MMSS CPR3 controllers each support one CPR thread that monitors the voltage of
+the graphics processor (MMSS) supply regulator. The CPR open-loop voltages are
+stored in hardware fuses for MMSS CPR3 controllers. However, the CPR target
+quotients must be defined in device tree.
+
+This document describes the MMSS specific CPR3 bindings.
+
+=======================
+Required Node Structure
+=======================
+
+CPR3 regulators must be described in three levels of devices nodes. The first
+level describes the CPR3 controller. The second level describes exacly one
+hardware thread managed by the controller. The third level describes one or
+more logical regulators handled by the CPR thread.
+
+All platform independent cpr3-regulator binding guidelines defined in
+cpr3-regulator.txt also apply to cpr3-hmss-regulator devices.
+
+====================================
+First Level Nodes - CPR3 Controllers
+====================================
+
+MMSS specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be one of the following:
+ "qcom,cpr3-msm8996-v1-mmss-regulator",
+ "qcom,cpr3-msm8996-v2-mmss-regulator",
+ "qcom,cpr3-msm8996-v3-mmss-regulator",
+ "qcom,cpr3-msm8996-mmss-regulator",
+ "qcom,cpr3-msm8996pro-mmss-regulator",
+ "qcom,cpr4-msm8998-v1-mmss-regulator",
+ "qcom,cpr4-msm8998-v2-mmss-regulator",
+ "qcom,cpr4-msm8998-mmss-regulator".
+ If the SoC revision is not specified, then it is assumed to
+ be the most recent revision (i.e v3 for MSM8996 and v2
+ for MSM8998).
+
+- clocks
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Array of clock tuples in which each tuple consists of a
+ phandle to a clock device and a clock ID number. The
+ following clocks must be specified: MMSS RBCPR, MMSS RBCPR
+ AHB, and MMSS MMAGIC AHB.
+
+- clock-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Clock names. This list must match up 1-to-1 with the clocks
+ specified in the 'clocks' property. "core_clk", "iface_clk",
+ and "bus_clk" must be specified. Note that "iface_clk" is
+ not required for devices with compatible =
+ "qcom,cpr4-msm8998-mmss-regulator".
+
+- qcom,cpr-temp-point-map
+ Usage: Required if qcom,corner-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <prop-encoded-array>
+ Definition: The temperature points in decidegrees Celsius which indicate
+ the range of temperature bands supported. If t1, t2, and t3
+ are the temperature points, then the temperature bands are:
+ (-inf, t1], (t1, t2], (t2, t3], and (t3, inf). 1 to 3
+ temperature points should be specified.
+
+- qcom,cpr-initial-temp-band
+ Usage: Required if qcom,cpr-temp-point-map is specified.
+ Value type: <u32>
+ Definition: The initial temp band considering 0-based index at which
+ the baseline target quotients are derived and fused.
+ Supported values: 0 to number of elements in
+ qcom,cpr-temp-point-map.
+
+- qcom,cpr-step-quot-fixed
+ Usage: Optional for controllers with compatible =
+ "qcom,cpr4-msm8998-mmss-regulator"; unsupported for
+ all others.
+ Value type: <u32>
+ Definition: Fixed step quotient value used by controller for applying
+ the SDELTA margin adjustments on the programmed target
+ quotient values. The step quotient is the number of
+ additional ring oscillator ticks observed for each
+ qcom,voltage-step increase in vdd-supply output voltage.
+ Supported values: 0 - 63.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+MMSS specific properties:
+N/A
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+MMSS specific properties:
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. This value must be 4
+ for MMSS.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. For MMSS the maximum
+ supported value is 8. These combos correspond to CPR
+ revision fuse values from 0 to 7 in order.
+
+- qcom,cpr-speed-bins
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the number of speed bins being supported by the
+ device. This value is utilized by several other properties.
+ Supported values are 1 up to the maximum possible for a
+ given regulator type. For MMSS the maximum supported value
+ is 1.
+
+- qcom,mem-acc-voltage
+ Usage: required if mem-acc-supply is specified for the CPR3 controller
+ containing this CPR3 regulator
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the mem-acc-supply
+ corner for each voltage corner in order from lowest to highest.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+- qcom,cpr-target-quotients
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists. Each tuple defines the
+ CPR target quotient for each ring oscillator (RO) for a
+ given corner. Since CPR3 supports exactly 16 ROs, each
+ tuple must contain 16 elements corresponding to RO0 through
+ RO15 in order. If a given RO is unused for a corner, then
+ its target quotient should be specified as 0.
+
+ Each tuple list in the grouping must meet the same size
+ requirements as those specified for qcom,mem-acc-voltage
+ above. The tuples in a given list are ordered from the
+ lowest corner to the highest corner.
+
+- qcom,cpr-ro-scaling-factor
+ Usage: required if qcom,cpr-closed-loop-voltage-adjustment is
+ specified
+ Value type: <prop-encoded-array>
+ Definition: The common definition of this property in cpr3-regulator.txt
+ is accurate for MMSS CPR3 controllers except for this
+ modification:
+
+ Each tuple list must contain the number of tuples defined in
+ the corresponding element of the qcom,cpr-corners property
+ or the qcom,cpr-speed-bins property as opposed to the value
+ of the qcom,cpr-fuse-corners property.
+
+- qcom,cpr-fused-closed-loop-voltage-adjustment-map
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR fused
+ corner closed-loop offset adjustment fuse to utilize for
+ each voltage corner in order from lowest to highest.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+ Each tuple element must be either 0 or in the range 1 to
+ qcom,cpr-fuse-corners. A value of 0 signifies that no fuse
+ based adjustment should be applied to the fuse corner.
+ Values 1 to qcom,cpr-fuse-corners denote the specific fuse
+ corner that should be used by a given voltage corner.
+
+- qcom,corner-allow-temp-adjustment
+ Usage: Optional for controllers with compatible =
+ "qcom,cpr4-msm8998-mmss-regulator"; unsupported for
+ all others.
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR
+ temperature adjustment feature enable state for each voltage
+ corner in order from lowest to highest. Each element in the
+ tuple should be either 0 (temperature adjustment not
+ allowed) or 1 (temperature adjustment allowed).
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bin-corners property. A single tuple may
+ only be specified if all of the corner counts in
+ qcom,cpr-corners are the same.
+
+- qcom,cpr-cornerX-temp-core-voltage-adjustment
+ Usage: Required if qcom,corner-allow-temp-adjustment is specified
+ for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples for cornerX. The possible values
+ for X are 1 to the max value specified in qcom,cpr-corners.
+ Each tuple defines the temperature based voltage adjustment
+ in microvolts for each temperature band from lowest to
+ highest. Each tuple must have a number of elements equal to
+ (the number of elements in qcom,cpr-ctrl-temp-point-map + 1)
+
+ The tuple list must contain qcom,cpr-fuse-combos number of
+ tuples in which case the tuples are matched to fuse
+ combinations 1-to-1 or qcom,cpr-speed-bins number of tuples
+ in which case the tuples are matched to speed bins 1-to-1 or
+ exactly 1 list which is used regardless of the fuse
+ combination and speed bin found on a given chip.
+
+Note that the qcom,cpr-closed-loop-voltage-fuse-adjustment property is not
+meaningful for MMSS CPR3 regulator nodes since target quotients are not defined
+in fuses.
+
+=======
+Example
+=======
+
+gfx_cpr: cpr3-ctrl@838000 {
+ compatible = "qcom,cpr3-msm8996-mmss-regulator";
+ reg = <0x00838000 0x4000>, <0x00074000 0x1000>;
+ reg-names = "cpr_ctrl", "fuse_base";
+ clocks = <&clock_mmss clk_mmss_rbcpr_clk>,
+ <&clock_mmss clk_mmss_rbcpr_ahb_clk>,
+ <&clock_mmss clk_mmss_mmagic_ahb_clk>;
+ clock-names = "core_clk", "iface_clk", "bus_clk";
+ interrupts = <0 166 0>;
+ interrupt-names = "cpr";
+ qcom,cpr-ctrl-name = "gfx";
+
+ qcom,cpr-sensor-time = <1000>;
+ qcom,cpr-loop-time = <5000000>;
+ qcom,cpr-idle-cycles = <15>;
+ qcom,cpr-step-quot-init-min = <13>;
+ qcom,cpr-step-quot-init-max = <13>;
+ qcom,cpr-count-mode = <2>;
+
+ vdd-supply = <&pmi8994_s2>;
+ qcom,voltage-step = <5000>;
+
+ qcom,cpr-enable;
+
+ qcom,cpr-aging-ref-voltage = <905000>;
+
+ thread@0 {
+ qcom,cpr-thread-id = <0>;
+ qcom,cpr-consecutive-up = <0>;
+ qcom,cpr-consecutive-down = <2>;
+ qcom,cpr-up-threshold = <0>;
+ qcom,cpr-down-threshold = <2>;
+
+ gfx_vreg: regulator {
+ regulator-name = "gfx_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <4>;
+
+ qcom,cpr-fuse-corners = <4>;
+ qcom,cpr-fuse-combos = <1>;
+ qcom,cpr-corners = <4>;
+
+ qcom,cpr-corner-fmax-map = <1 2 3 4>;
+
+ qcom,cpr-voltage-ceiling =
+ <670000 745000 905000 1015000>;
+ qcom,cpr-voltage-floor =
+ <545000 625000 755000 855000>;
+
+ qcom,mem-acc-voltage = <1 1 2 2>;
+
+ qcom,corner-frequencies =
+ <120000000 205000000 360000000
+ 480000000>;
+
+ qcom,cpr-target-quotients =
+ < 0 0 0 0 249 232 0 394
+ 0 422 0 0 0 0 0 0>,
+ < 0 0 0 0 400 363 0 565
+ 0 603 0 0 0 0 0 0>,
+ < 0 0 0 0 669 601 0 851
+ 0 905 0 0 0 0 0 0>,
+ < 0 0 0 0 899 806 0 1084
+ 0 1149 0 0 0 0 0 0>;
+
+ qcom,cpr-ro-scaling-factor =
+ < 0 0 0 0 2268 2004 0 2408
+ 0 2539 0 0 0 0 0 0>,
+ < 0 0 0 0 2268 2004 0 2408
+ 0 2539 0 0 0 0 0 0>,
+ < 0 0 0 0 2268 2004 0 2408
+ 0 2539 0 0 0 0 0 0>,
+ < 0 0 0 0 2268 2004 0 2408
+ 0 2539 0 0 0 0 0 0>;
+
+ qcom,cpr-open-loop-voltage-fuse-adjustment =
+ <35000 0 0 0>;
+ qcom,cpr-closed-loop-voltage-adjustment =
+ <45000 0 0 0>;
+
+ qcom,cpr-fused-closed-loop-voltage-adjustment-map =
+ <2 2 0 4>;
+
+ qcom,allow-voltage-interpolation;
+ qcom,cpr-scaled-open-loop-voltage-as-ceiling;
+
+ qcom,cpr-aging-max-voltage-adjustment = <25000>;
+ qcom,cpr-aging-ref-corner = <3>;
+ qcom,cpr-aging-ro-scaling-factor = <2950>;
+ qcom,cpr-aging-derate =
+ <1000 1000 1000 1000>;
+ qcom,allow-aging-voltage-adjustment = <1>;
+ };
+ };
+};
diff --git a/Documentation/devicetree/bindings/regulator/cpr3-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr3-regulator.txt
new file mode 100644
index 0000000..af53e59
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cpr3-regulator.txt
@@ -0,0 +1,622 @@
+Qualcomm Technologies, Inc. CPR3 Regulator - Platform Independent Bindings
+
+Core Power Reduction (CPR) version 3 controllers are used by some Qualcomm
+Technologies, Inc. (QTI) 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.
+
+This document describes the common platform independent bindings that apply
+to all CPR3 controllers.
+
+=======================
+Required Node Structure
+=======================
+
+CPR3 regulators must be described in three levels of devices nodes. The first
+level describes the CPR3 controller. The second level describes one or more
+hardware threads managed by the controller. The third level describes one or
+more logical regulators handled by each CPR thread.
+
+====================================
+First Level Nodes - CPR3 Controllers
+====================================
+
+Platform independent properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: The value to use for this property is defined in the
+ platform specific cpr3-regulator binding documentation
+ files.
+
+- reg
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Addresses and sizes for the memory of the CPR3 controller,
+ the first fuse row, and optionally a register used to check
+ if aging measurements are possible.
+
+- reg-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Address names. Must include "cpr_ctrl" and "fuse_base".
+ "aging_allowed" may also be specified. The strings must be
+ specified in the same order as the corresponding addresses
+ are specified in the reg property.
+
+- qcom,cpr-ctrl-name
+ Usage: required
+ Value type: <string>
+ Definition: Name for this CPR controller
+
+- vdd-supply
+ Usage: required
+ Value type: <phandle>
+ Definition: phandle of the underlying regulator device that is managed
+ by this CPR controller.
+
+- system-supply
+ Usage: optional
+ Value type: <phandle>
+ Definition: phandle of the system-level regulator device which the
+ vdd-supply depends upon. Requests for this regulator must
+ be made before increasing the vdd-supply voltage and after
+ decreasing the vdd-supply voltage.
+
+- mem-acc-supply
+ Usage: optional
+ Value type: <phandle>
+ Definition: phandle of the mem-acc regulator device which is used to
+ configure memory array circuitry settings based upon
+ the performance operating point. Requests for this regulator
+ must be made before decreasing the vdd-supply voltage and
+ after increasing the vdd-supply voltage.
+
+- clocks
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: Array of clock tuples in which each tuple consists of a
+ phandle to a clock device and a clock ID number. The CPR3
+ core clock must be specified for some targets. See platform
+ specific cpr3-regulator binding documentation for additional
+ clocks that may also need to be specified.
+
+- clock-names
+ Usage: optional
+ Value type: <stringlist>
+ Definition: Clock names. This list must match up 1-to-1 with the clocks
+ specified in the 'clocks' property. "core_clk" must be
+ specified for some platforms. Other clocks may be required
+ for some platforms.
+
+- interrupts
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: CPR interrupt specifier and optionally a hardware
+ closed-loop ceiling interrupt specifier.
+
+- interrupt-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Interrupt names. This list must match up 1-to-1 with the
+ interrupts specified in the 'interrupts' property. "cpr"
+ must be specified. "ceiling" may be specified for some
+ platforms.
+
+- qcom,cpr-interrupt-affinity
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of CPU phandles which correspond to the cores that
+ the "cpr" interrupt should have affinity for.
+
+- qcom,cpr-sensor-time
+ Usage: required
+ Value type: <u32>
+ Definition: The time in nanoseconds that each CPR sensor within the
+ sensing loop takes to perform a measurement.
+
+- qcom,cpr-loop-time
+ Usage: required
+ Value type: <u32>
+ Definition: The time in nanoseconds between consecutive CPR
+ measurements.
+
+- qcom,cpr-idle-cycles
+ Usage: required
+ Value type: <u32>
+ Definition: The number of CPR core clock cycles for the CPR controller
+ to wait in transitional states.
+ Supported values: 0 - 31.
+
+- qcom,voltage-step
+ Usage: required
+ Value type: <u32>
+ Definition: The voltage in microvolts of a single step of the VDD supply
+ regulator being controlled by CPR.
+
+- qcom,cpr-step-quot-init-min
+ Usage: required
+ Value type: <u32>
+ Definition: The default minimum CPR step quotient value. The step
+ quotient is the number of additional ring oscillator ticks
+ observed for each qcom,voltage-step increase in vdd-supply
+ output voltage. Supported values: 0 - 63.
+
+- qcom,cpr-step-quot-init-max
+ Usage: required
+ Value type: <u32>
+ Definition: The default maximum CPR step quotient value.
+ Supported values: 0 - 63.
+
+- qcom,cpr-count-mode
+ Usage: required
+ Value type: <u32>
+ Definition: The CPR counting mode to use during CPR measurements.
+ Supported values:
+ 0 - Read all sensors at once and use the minimum
+ quotient value observed in repeated measurements.
+ 1 - Read all sensors at once and use the maximum
+ quotient value observed in repeated measurements.
+ 2 - Read each sensor once in a sequential, staggered
+ fashion.
+
+- qcom,cpr-count-repeat
+ Usage: optional
+ Value type: <u32>
+ Definition: The number of times to read CPR sensors during a single CPR
+ measurement when using one of the all-at-once count modes.
+
+- qcom,cpr-enable
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the CPR3 controller
+ should operate in closed-loop mode (i.e. CPR sensing loop
+ enabled) as opposed to open-loop mode (i.e. CPR sensing loop
+ disabled) by default.
+
+- qcom,cpr-aging-ref-voltage
+ Usage: required if qcom,allow-aging-voltage-adjustment is specified
+ for any third level nodes
+ Value type: <u32>
+ Definition: Specifies the CPR aging reference voltage in microvolts.
+ This is the voltage that vdd-supply must be set to when
+ performing an aging measurement.
+
+- qcom,cpr-aging-allowed-reg-mask
+ Usage: required if "aging_allowed" register is specified
+ Value type: <u32>
+ Definition: Bitmask used to mask off the "aging_allowed" register.
+
+- qcom,cpr-aging-allowed-reg-value
+ Usage: required if "aging_allowed" register is specified
+ Value type: <u32>
+ Definition: Value required in the masked off "aging_allowed" register
+ bits in order for a CPR aging measurement to be possible.
+
+- qcom,cpr-panic-reg-addr-list
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: Array of register addresses to be dumped when device resets.
+
+- qcom,cpr-panic-reg-name-list
+ Usage: optional, though only meaningful if
+ qcom,cpr-panic-reg-addr-list is specified
+ Value type: <prop-encoded-array>
+ Definition: Address names. Must be specified in the same order
+ as the corresponding addresses are specified in
+ the qcom,cpr-panic-reg-addr-list property.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+Platform independent properties:
+- qcom,cpr-thread-id
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the hardware thread ID of this thread within the
+ CPR controller.
+
+- qcom,cpr-consecutive-up
+ Usage: required
+ Value type: <u32>
+ Definition: The number of consecutive CPR step up events needed to
+ to trigger an up interrupt. Supported values: 0 - 15.
+
+- qcom,cpr-consecutive-down
+ Usage: required
+ Value type: <u32>
+ Definition: The number of consecutive CPR step down events needed to
+ to trigger a down interrupt. Supported values: 0 - 15.
+
+- qcom,cpr-up-threshold
+ Usage: required
+ Value type: <u32>
+ Definition: The number CPR error steps required to generate an up event.
+ Supported values: 0 - 31.
+
+- qcom,cpr-down-threshold
+ Usage: required
+ Value type: <u32>
+ Definition: The number CPR error steps required to generate a down
+ event. Supported values: 0 - 31.
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+Platform independent properties:
+- regulator-name
+ Usage: required
+ Value type: <string>
+ Definition: Specifies the name for this CPR3 regulator.
+
+- regulator-min-microvolt
+ Usage: required
+ Value type: <u32>
+ Definition: Minimum corner value which should be 1 to represent the
+ lowest supported corner.
+
+- regulator-max-microvolt
+ Usage: required
+ Value type: <u32>
+ Definition: Maximum corner value which should be equal to largest value
+ listed in qcom,cpr-corners.
+
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. See platform specific
+ binding files for further requirements.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. See platform specific
+ binding files for further details.
+
+- qcom,cpr-speed-bins
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the number of speed bins being supported by the
+ device. This value is utilized by several other properties.
+ Supported values are 1 up to the maximum possible for a
+ given regulator type. See platform specific binding files
+ for further details.
+
+ This property can only be utilized if the number of corners
+ for all fuse combinations associated with a given speed bin
+ is the same.
+
+- qcom,cpr-corners
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines how many voltage corners
+ are to be used for each fuse combination. The list must
+ contain either qcom,cpr-fuse-combos number of elements in
+ which case the corner counts are applied to fuse
+ combinations 1-to-1 or the list must contain exactly 1
+ element which is used regardless of the fuse combination
+ found on a given chip.
+
+- qcom,cpr-speed-bin-corners
+ Usage: required if qcom,cpr-speed-bins is specified
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines how many voltage corners
+ are to be used for each speed bin. The list must contain
+ qcom,cpr-speed-bins number of elements.
+
+- qcom,cpr-corner-fmax-map
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the highest
+ (i.e. maximum frequency) 1-based corner value associated
+ with each fuse-corner.
+
+ Each tuple must have a number of elements equal to the value
+ of the qcom,cpr-fuse-corners property. The elements of a
+ tuple are ordered from lowest to highest fuse corner.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+- qcom,cpr-voltage-ceiling
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR ceiling
+ voltage in microvolts for each voltage corner in order from
+ lowest to highest.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+- qcom,cpr-voltage-floor
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR floor
+ voltage in microvolts for each voltage corner in order from
+ lowest to highest.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+- qcom,cpr-floor-to-ceiling-max-range
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the maximum
+ allowed difference between the final floor voltage and the
+ final ceiling voltage in microvolts for each voltage corner
+ in order from lowest to highest. A negative value may be
+ specified for an element to indicate that there is no
+ limitation of the floor to ceiling voltage range for the
+ corresponding corner.
+
+ In the case that the initial floor to ceiling voltage is
+ greater than the max range specified, the floor voltage will
+ be increased in order to satisfy the max range constraint.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+- qcom,system-voltage
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the system-supply
+ voltage in microvolts or corners or levels for each voltage
+ corner in order from lowest to highest.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+- qcom,corner-frequencies
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPU frequency
+ in Hertz corresponding to each voltage corner in order from
+ lowest to highest.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+- qcom,allow-voltage-interpolation
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that it is acceptable to use
+ interpolated open-loop voltage values. These values are
+ interpolated between the open-loop voltage Fmax fuse values.
+
+- qcom,cpr-scaled-open-loop-voltage-as-ceiling
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that it is acceptable to use
+ the interpolated open-loop voltage for each corner as the
+ CPR ceiling voltage for each corner.
+
+- qcom,cpr-open-loop-voltage-fuse-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the open-loop
+ voltage adjustment in microvolts for each fused voltage
+ corner in order from lowest to highest. This adjustment is
+ applied to the values read from fuses before the values are
+ used in interpolation for intermediate corners.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-corner-fmax-map above.
+
+ The open-loop voltage for a given fuse corner corresponds to
+ the voltage that is safe to use under all circumstances.
+ It is used as a starting voltage for CPR and may also be
+ specified as a ceiling voltage for CPR scaling.
+
+- qcom,cpr-open-loop-voltage-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the open-loop
+ voltage adjustment in microvolts for each voltage corner in
+ order from lowest to highest. This adjustment is applied to
+ the open-loop voltage values after they have been
+ interpolated for intermediate corners.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+- qcom,cpr-open-loop-voltage-min-diff
+ Usage: optional; only meaningful if the
+ qcom,cpr-open-loop-voltage-adjustment property is specified
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the minimum
+ allowed open-loop voltage difference in microvolts between
+ each voltage corner and the one immediately preceding it.
+ The elements in a tuple are ordered from the lowest to the
+ highest corner. The value specified for the first corner is
+ ignored since there is no corner before it.
+
+ Negative voltage values may be specified for this property.
+ A negative value means that the open-loop voltage of a
+ corner may be lower than that of the preceding corner.
+
+ The minimum difference is enforced after the open-loop
+ voltage values have been interpolated for intermediate
+ corners and after adjustments have been applied.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+ If this property is not specified, then the minimum
+ difference is assumed to be 0 uV for all corners.
+
+- qcom,cpr-closed-loop-voltage-fuse-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the closed-loop
+ voltage adjustment in microvolts for each fused voltage
+ corner in order from lowest to highest. This adjustment is
+ applied to the values read from fuses before the values are
+ used in interpolation for intermediate corners.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-corner-fmax-map above.
+
+ The qcom,cpr-ro-scaling-factor property must be specified in
+ order to utilize this property.
+
+ The closed-loop voltage for a given fuse corner corresponds
+ to the voltage that the CPR controller settles the VDD
+ supply rail to based upon the programmed CPR target
+ quotients and the current silicon conditions.
+
+- qcom,cpr-closed-loop-voltage-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the closed-loop
+ voltage adjustment in microvolts for each voltage corner in
+ order from lowest to highest. This adjustment is applied to
+ target quotient values after they have been interpolated
+ for intermediate corners.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+ The qcom,cpr-ro-scaling-factor property must be specified in
+ order to utilize this property.
+
+- qcom,cpr-ro-scaling-factor
+ Usage: required if qcom,cpr-closed-loop-voltage-fuse-adjustment,
+ qcom,cpr-closed-loop-voltage-adjustment, or
+ qcom,allow-aging-voltage-adjustment is specified
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists. Each tuple defines the
+ CPR ring oscillator (RO) scaling factor with units of QUOT/V
+ for each RO for a given fuse corner. Since CPR3 supports
+ exactly 16 ROs, each tuple must contain 16 elements
+ corresponding to RO0 through RO15 in order. If a given RO
+ is unused for a fuse corner, then its scaling factor may be
+ specified as 0.
+
+ Each tuple list must contain the number of tuples defined in
+ the qcom,cpr-fuse-corners property. The tuples in a given
+ list are ordered from the lowest fuse corner to the highest
+ fuse corner.
+
+ The tuple list grouping must contain qcom,cpr-fuse-combos
+ number of tuple lists in which case the lists are matched to
+ fuse combinations 1-to-1 or qcom,cpr-speed-bins number of
+ tuple lists in which case the lists are matched to
+ speed bins 1-to-1 or exactly 1 list which is used regardless
+ of the fuse combination and speed bin found on a given chip.
+
+ The target quotient adjustment to apply for each RO of a
+ given corner is determined by multiplying the adjustment
+ value in qcom,cpr-closed-loop-voltage-fuse-adjustment or
+ qcom,cpr-closed-loop-voltage-adjustment by the relevant RO
+ scaling factor in this property.
+
+- qcom,allow-aging-voltage-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which specifies if CPR aging adjustment
+ should be performed for each fuse combination.
+ Supported per-combo element values:
+ 0 - do not perform CPR aging adjustment
+ 1 - perform CPR aging adjustment
+
+ The list must contain qcom,cpr-fuse-combos number of
+ elements in which case the elements are matched to fuse
+ combinations 1-to-1 or qcom,cpr-speed-bins number of
+ elements in which case the elements are matched to
+ speed bins 1-to-1 or exactly 1 element which is used
+ regardless of the fuse combination and speed bin found
+ on a given chip.
+
+- qcom,allow-aging-open-loop-voltage-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which specifies if CPR aging adjustment
+ should be applied to open-loop voltages for each fuse
+ combination. Note that aging adjustment must be allowed via
+ qcom,allow-aging-voltage-adjustment in order for this
+ property to have an effect.
+ Supported per-combo element values:
+ 0 - do not perform CPR aging adjustment
+ 1 - perform CPR aging adjustment
+
+ The list must meet the same size requirements as those
+ specified for qcom,allow-aging-voltage-adjustment above.
+
+- qcom,cpr-aging-max-voltage-adjustment
+ Usage: required if qcom,allow-aging-voltage-adjustment is specified
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines the maximum CPR aging
+ voltage margin adjustment in microvolts that may be added
+ for each fuse combination. If this property is specified
+ and the adjustment specified is greater than 0, then aging
+ adjustments are required for this regulator.
+
+ The list must meet the same size requirements as those
+ specified for qcom,allow-aging-voltage-adjustment above.
+
+- qcom,cpr-aging-ref-corner
+ Usage: required if qcom,allow-aging-voltage-adjustment is specified
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines the CPR reference corner
+ for this regulator to use during aging measurements for each
+ fuse combination.
+
+ The list must meet the same size requirements as those
+ specified for qcom,allow-aging-voltage-adjustment above.
+
+- qcom,cpr-aging-ro-scaling-factor
+ Usage: required if qcom,allow-aging-voltage-adjustment is specified
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines the CPR aging ring
+ oscillator (RO) scaling factor with units of QUOT/V to use
+ during aging measurements for each fuse combination.
+
+ The list must meet the same size requirements as those
+ specified for qcom,allow-aging-voltage-adjustment above.
+
+- qcom,cpr-aging-derate
+ Usage: optional, though only meaningful if
+ qcom,allow-aging-voltage-adjustment is specified
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR aging
+ derating scaling factor to apply to the closed-loop voltage
+ margin adjustment for each corner. The individual scaling
+ factors have units of uV/mV and are ordered from lowest to
+ highest corner per tuple. For example, a value of 900
+ specifies that the voltage adjustment for the corner should
+ be 90% (900/1000) of that for the reference corner.
+
+ The list and tuples must meet the same size requirements as
+ those specified for qcom,cpr-voltage-ceiling above.
+
+ If this property is not specified, then it is assumed that
+ no corners require derating (i.e. the scaling factor would
+ be 1000).
+
+All properties specified within the core regulator framework can also be used in
+third level nodes. These bindings can be found in:
+Documentation/devicetree/bindings/regulator/regulator.txt.
+
+See platform specific cpr3-regulator binding documentation files for examples.
diff --git a/Documentation/devicetree/bindings/regulator/cpr4-apss-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr4-apss-regulator.txt
new file mode 100644
index 0000000..29bb2d3
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cpr4-apss-regulator.txt
@@ -0,0 +1,522 @@
+Qualcomm Technologies, Inc. CPR4 Regulator - APSS Specific Bindings
+
+APSS CPR4 controllers each support one CPR thread that monitors the voltage of
+a pair of application processor (APSS) clusters that are powered by a shared
+regulator supply. They also have a hardware channel to use these requests to
+directly change the supply voltage at the PMIC via the SPM without software
+intervention.
+
+APSS CPR4 controllers also have to take into account the state of the memory
+array power mux (APM) when scaling voltage to ensure that memory always receives
+a sufficiently high voltage.
+
+Both CPR open-loop voltages and CPR target quotients are stored in hardware
+fuses for APSS CPR4 controllers.
+
+This document describes the APSS specific CPR4 bindings.
+
+=======================
+Required Node Structure
+=======================
+
+CPR4 regulators must be described in three levels of devices nodes. The first
+level describes the CPR4 controller. The second level describes one or more
+hardware threads managed by the controller. The third level describes one or
+more logical regulators handled by each CPR thread.
+
+All platform independent cpr3-regulator binding guidelines defined in
+cpr3-regulator.txt also apply to cpr4-apss-regulator devices.
+
+====================================
+First Level Nodes - CPR4 Controllers
+====================================
+
+APSS specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be one of the following:
+ "qcom,cpr4-msm8953-apss-regulator";
+
+- interrupts
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: CPR interrupt specifier.
+
+- interrupt-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Interrupt names. This list must match up 1-to-1 with the
+ interrupts specified in the 'interrupts' property. "cpr"
+ must be specified.
+
+- qcom,apm-ctrl
+ Usage: required on systems that need APM management
+ Value type: <phandle>
+ Definition: phandle of memory array power mux (APM) controller device
+ node for the APM that is used by the APSS VDD supply
+
+- qcom,apm-threshold-voltage
+ Usage: required if qcom,apm-ctrl is specified
+ Value type: <u32>
+ Definition: Specifies the APM threshold voltage in microvolts. If the
+ vdd-supply voltage is greater than or equal to this level,
+ then the APM is switched to use the vdd-supply. If the
+ vdd-supply voltage is below this level, then the APM is
+ switched to use the system-supply.
+
+- qcom,apm-hysteresis-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the voltage delta in microvolts between the APM
+ threshold voltage and the highest corner open-loop voltage
+ which may be used as the ceiling for the corner. If this
+ property is not specified, then a value of 0 is assumed.
+
+- qcom,cpr-hw-closed-loop
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the APSS CPR4 controller
+ should operate in hardware closed-loop mode as opposed to
+ software closed-loop mode.
+
+- vdd-limit-supply
+ Usage: required
+ Value type: <phandle>
+ Definition: phandle of the VDD supply limit regulator which controls the
+ CPR ceiling and floor voltages when operating in hardware
+ closed-loop mode.
+
+- qcom,cpr-down-error-step-limit
+ Usage: required
+ Value type: <u32>
+ Definition: 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.
+
+- qcom,cpr-up-error-step-limit
+ Usage: required
+ Value type: <u32>
+ Definition: 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.
+
+- qcom,cpr-saw-use-unit-mV
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the unit used in SAW PVC
+ interface is mV. Use this for vdd-supply regulators which
+ do not use PMIC voltage control register LSBs per actually
+ unique PMIC regulator output voltage.
+
+- qcom,cpr-temp-point-map
+ Usage: required if qcom,corner-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <prop-encoded-array>
+ Definition: The temperature points in decidegrees Celsius which indicate
+ the range of temperature bands supported. If t1, t2, and t3
+ are the temperature points, then the temperature bands are:
+ (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).
+
+- qcom,cpr-initial-temp-band
+ Usage: required if qcom,corner-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <u32>
+ Definition: The initial temp band considering 0-based index at which
+ the baseline target quotients are derived and fused.
+
+- qcom,cpr-step-quot-fixed
+ Usage: optional
+ Value type: <u32>
+ Definition: Fixed step quotient value used by controller for applying
+ the SDELTA margin adjustments on the programmed target
+ quotient values. The step quotient is the number of
+ additional ring oscillator ticks observed for each
+ qcom,voltage-step increase in vdd-supply output voltage.
+ Supported values: 0 - 63.
+
+- qcom,cpr-voltage-settling-time
+ Usage: optional
+ Value type: <u32>
+ The time in nanoseconds that it takes for the vdd-supply
+ voltage to settle after being increased or decreased by
+ qcom,voltage-step microvolts. This is used as the wait
+ time after applying SDELTA voltage margin adjustments.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+APSS specific properties:
+N/A
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+APSS specific properties:
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. This value must be 4
+ for APSS. These fuse corners are: LowSVS, SVS, Nominal,
+ and Turbo.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. For APSS the maximum
+ supported value is 64. The first 8 fuse combos correspond
+ to speed bin fuse value 0 along with CPR revision fuse
+ values 0 to 7. The second 8 fuse combos correspond to speed
+ bin fuse value 1 along with CPR revision fuse values 0 to 7.
+ The last 8 fuse combos correspond to speed bin fuse value 7
+ along with CPR revision fuse values 0 to 7.
+
+- qcom,cpr-speed-bins
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the number of speed bins being supported by the
+ device. This value is utilized by several other properties.
+ Supported values are 1 up to the maximum possible for a
+ given regulator type. For APSS the maximum supported value
+ is 8.
+
+- qcom,mem-acc-voltage
+ Usage: required if mem-acc-supply is specified for the CPR4 controller
+ containing this regulator
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the mem-acc-supply
+ corner for each voltage corner in order from lowest to highest.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+- qcom,allow-quotient-interpolation
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that it is acceptable to use
+ interpolated CPR target quotient values. These values are
+ interpolated between the target quotient Fmax fuse values.
+
+- qcom,corner-allow-core-count-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR core
+ count adjustment feature enable state for each voltage
+ corner in order from lowest to highest. Each element in
+ the tuple should be either 0 (per-core-count adjustment
+ not allowed) or 1 (per-core-count adjustment allowed).
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+- qcom,max-core-count
+ Usage: required if qcom,corner-allow-core-count-adjustment is
+ specified for this CPR3 regulator.
+ Value type: <u32>
+ Definition: The maximum number of cores considered for core-count vmin
+ adjustments specified for this regulator voltage corners.
+
+- qcom,corner-allow-temp-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR
+ temperature adjustment feature enable state for each voltage
+ corner in order from lowest to highest. Each element in the
+ tuple should be either 0 (temperature adjustment not
+ allowed) or 1 (temperature adjustment allowed).
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+- qcom,cpr-cornerX-temp-core-voltage-adjustment
+ Usage: required if qcom,corner-allow-core-count-adjustment
+ specified for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists for cornerX. The possible
+ values for X are 1 to the max value specified in
+ qcom,cpr-corners. Each tuple defines the temperature based
+ voltage adjustment in microvolts for each temperature band
+ from lowest to highest for a given number of online cores.
+ Each tuple must have a number of elements equal to either
+ (the number of elements in qcom,cpr-ctrl-temp-point-map
+ + 1), if qcom,cpr-ctrl-temp-point-map is specified, or 1.
+
+ Each tuple list must contain a number of tuples equal to
+ either qcom,max-core-count, if qcom,max-core-count is
+ specified, or 1. The tuples should be ordered from lowest
+ to highest core count.
+
+ The tuple list grouping must contain qcom,cpr-fuse-combos
+ number of tuple lists in which case the lists are matched to
+ fuse combinations 1-to-1 or qcom,cpr-speed-bins number of
+ tuple lists in which case the lists are matched to
+ speed bins 1-to-1 or exactly 1 list which is used regardless
+ of the fuse combination and speed bin found on a given chip.
+
+- qcom,cpr-num-boost-cores
+ Usage: required if qcom,allow-boost specified for this CPR3
+ regulator.
+ Value type: <u32>
+ Definition: Integer value indicates that voltage boost will be applied
+ when the number of online cores become this value.
+
+- qcom,cpr-boost-temp-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the temperature
+ based voltage adjustment to boost voltage in microvolts
+ for each temperature band in order from lowest to highest.
+
+ The number of elements in each tuple should be equal to either
+ (the number of elements in qcom,cpr-ctrl-temp-point-map
+ + 1), if qcom,cpr-ctrl-temp-point-map is specified, or 1.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+- qcom,allow-boost
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which specifies if the voltage boost
+ feature should be enabled for each fuse combination.
+ Supported per-combo element values:
+ 0 - voltage boost feature disabled
+ 1 - voltage boost feature enabled
+
+ The list must contain qcom,cpr-fuse-combos number of
+ elements in which case the elements are matched to fuse
+ combinations 1-to-1 or qcom,cpr-speed-bins number of
+ elements in which case the elements are matched to
+ speed bins 1-to-1 or exactly 1 element which is used
+ regardless of the fuse combination and speed bin found
+ on a given chip.
+
+- qcom,cpr-boost-voltage-fuse-adjustment
+ Usage: optional
+ Value type: <u32>
+ Definition: A list of integers which defines the voltage adjustment
+ in microvolts for the fused boost voltage. This adjustment
+ is applied to the values read from boost fuses.
+
+ The list must contain qcom,cpr-fuse-combos number of
+ elements in which case the elements are matched to fuse
+ combinations 1-to-1 or qcom,cpr-speed-bins number of
+ elements in which case the elements are matched to
+ speed bins 1-to-1 or exactly 1 element which is used
+ regardless of the fuse combination and speed bin found
+ on a given chip.
+
+- qcom,cpr-misc-fuse-voltage-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists where each tuple defines
+ the voltage adjustments in microvolts for each voltage
+ corner in order from lowest to highest. This adjustment is
+ applied to both open-loop and closed-loop voltages.
+
+ Each tuple list must contain a number of tuples equal to
+ 2 to the power of the number of bits selected for misc
+ voltage adj fuse definition. For MSM8953 the tuple
+ list must contain 2 tuples for the 1-bit misc fuse.
+ Tuples in a list should be specified in ascending order
+ according to the misc fuse value assuming that the fuse
+ is treated like an unsigned integer.
+
+ The tuple list grouping must contain qcom,cpr-speed-bins
+ number of tuple lists in which case the lists are matched to
+ speed bins 1-to-1 or exactly 1 list which is used regardless
+ of the speed bin found on a given chip.
+
+=======
+Example
+=======
+
+apc_cpr: cpr4-ctrl@b018000 {
+ compatible = "qcom,cpr4-msm8953-apss-regulator";
+ reg = <0xb018000 0x4000>, <0xa4000 0x1000>;
+ reg-names = "cpr_ctrl", "fuse_base";
+ interrupts = <GIC_SPI 15 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "cpr";
+
+ qcom,cpr-ctrl-name = "apc";
+
+ qcom,cpr-sensor-time = <1000>;
+ qcom,cpr-loop-time = <5000000>;
+ qcom,cpr-idle-cycles = <15>;
+ qcom,cpr-step-quot-init-min = <13>;
+ qcom,cpr-step-quot-init-max = <13>;
+ qcom,cpr-count-mode = <2>; /* Staggered */
+ qcom,cpr-down-error-step-limit = <1>;
+ qcom,cpr-up-error-step-limit = <1>;
+
+ qcom,apm-ctrl = <&apc_apm>;
+ qcom,apm-threshold-voltage = <848000>;
+ qcom,apm-hysteresis-voltage = <5000>;
+
+ vdd-supply = <&pm8953_s5>;
+ qcom,voltage-step = <5000>;
+ vdd-limit-supply = <&pm8953_s5_limit>;
+ mem-acc-supply = <&apc_mem_acc_vreg>;
+
+ qcom,cpr-enable;
+ qcom,cpr-hw-closed-loop;
+
+ qcom,cpr-temp-point-map = <0 250 850>;
+ qcom,cpr-initial-temp-band = <3>;
+ qcom,cpr-step-quot-fixed = <16>;
+ qcom,cpr-voltage-settling-time = <1600>;
+
+ qcom,cpr-panic-reg-addr-list =
+ <0xb1d2c18 0xb1d2900 0x0b1112b0 0xb018798>;
+ qcom,cpr-panic-reg-name-list =
+ "CCI_SAW4_PMIC_STS", "CCI_SAW4_VCTL",
+ "APCS_ALIAS0_APM_CTLER_STATUS",
+ "APCS0_CPR_CORE_ADJ_MODE_REG";
+
+ thread@0 {
+ qcom,cpr-thread-id = <0>;
+ qcom,cpr-consecutive-up = <1>;
+ qcom,cpr-consecutive-down = <1>;
+ qcom,cpr-up-threshold = <1>;
+ qcom,cpr-down-threshold = <1>;
+
+ apc_vreg: regulator {
+ regulator-name = "apc_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <8>;
+
+ qcom,cpr-fuse-corners = <4>;
+ qcom,cpr-fuse-combos = <8>;
+ qcom,cpr-speed-bins = <1>;
+ qcom,cpr-corners = <8>;
+
+ qcom,cpr-corner-fmax-map = <1 2 4 8>;
+
+ qcom,cpr-voltage-ceiling =
+ <645000 720000 790000 865000 920000
+ 990000 1065000 1065000>;
+
+ qcom,cpr-voltage-floor =
+ <590000 625000 690000 755000 800000
+ 855000 920000 920000>;
+
+ qcom,mem-acc-voltage = <1 1 2 2 2 2 2 2>;
+
+ qcom,corner-frequencies =
+ <652800000 1036800000 1401600000
+ 1689600000 1843200000 1958400000
+ 2150400000 2208000000>;
+
+ qcom,cpr-misc-fuse-voltage-adjustment =
+ /* Speed bin 0; misc fuse 0..1 */
+ < 0 0 0 0
+ 0 0 0 0>,
+ < 0 0 30000 0
+ 0 0 0 0>;
+
+ qcom,allow-voltage-interpolation;
+ qcom,allow-quotient-interpolation;
+ qcom,cpr-scaled-open-loop-voltage-as-ceiling;
+
+ qcom,corner-allow-temp-adjustment =
+ <0 0 0 1 0 1 1 1>;
+
+ qcom,corner-allow-core-count-adjustment =
+ <0 0 0 0 1 1 1 1>;
+ qcom,max-core-count = <8>;
+ qcom,cpr-corner4-temp-core-voltage-adjustment =
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>,
+ <(-20000) (-10000) (-5000) 0>;
+ qcom,cpr-corner5-temp-core-voltage-adjustment =
+ <(-50000) (-50000) (-50000) (-50000)>,
+ <(-50000) (-50000) (-50000) (-50000)>,
+ <(-40000) (-40000) (-40000) (-40000)>,
+ <(-40000) (-40000) (-40000) (-40000)>,
+ <(-30000) (-30000) (-30000) (-30000)>,
+ <(-30000) (-30000) (-30000) (-30000)>,
+ <(-20000) (-20000) (-20000) (-20000)>,
+ <(-20000) (-20000) (-20000) (-20000)>;
+ qcom,cpr-corner6-temp-core-voltage-adjustment =
+ <(-50000) (-40000) (-30000) (-20000)>,
+ <(-50000) (-40000) (-30000) (-20000)>,
+ <(-40000) (-30000) (-20000) (-10000)>,
+ <(-40000) (-30000) (-20000) (-10000)>,
+ <(-30000) (-20000) (-10000) (-5000)>,
+ <(-30000) (-20000) (-10000) (-5000)>,
+ <(-20000) (-10000) (-5000) 0 >,
+ <(-20000) (-10000) (-5000) 0 >;
+ qcom,cpr-corner7-temp-core-voltage-adjustment =
+ <(-50000) (-40000) (-30000) (-20000)>,
+ <(-50000) (-40000) (-30000) (-20000)>,
+ <(-40000) (-30000) (-20000) (-10000)>,
+ <(-40000) (-30000) (-20000) (-10000)>,
+ <(-30000) (-20000) (-10000) (-5000)>,
+ <(-30000) (-20000) (-10000) (-5000)>,
+ <(-20000) (-10000) (-5000) 0 >,
+ <(-20000) (-10000) (-5000) 0 >;
+ qcom,cpr-corner8-temp-core-voltage-adjustment =
+ <(-50000) (-40000) (-30000) (-20000)>,
+ <(-50000) (-40000) (-30000) (-20000)>,
+ <(-40000) (-30000) (-20000) (-10000)>,
+ <(-40000) (-30000) (-20000) (-10000)>,
+ <(-30000) (-20000) (-10000) (-5000)>,
+ <(-30000) (-20000) (-10000) (-5000)>,
+ <(-20000) (-10000) (-5000) 0 >,
+ <(-20000) (-10000) (-5000) 0 >;
+
+ qcom,cpr-num-boost-cores = <4>;
+ qcom,cpr-boost-voltage-fuse-adjustment = <(-10000)>;
+ qcom,cpr-boost-temp-adjustment =
+ <(-20000) (-15000) (-10000) 0>;
+ qcom,allow-boost =
+ <1>;
+ };
+ };
+};
diff --git a/Documentation/devicetree/bindings/regulator/cpr4-mmss-ldo-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr4-mmss-ldo-regulator.txt
new file mode 100644
index 0000000..8515285
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cpr4-mmss-ldo-regulator.txt
@@ -0,0 +1,321 @@
+Qualcomm Technologies, Inc. CPR4 Regulator - MMSS LDO Specific Bindings
+
+MMSS LDO CPR4 controllers each support one CPR thread that monitors the voltage
+of the graphics processor (MMSS) supply regulator. The CPR open-loop voltages
+are stored in hardware fuses for MMSS CPR4 controllers. However, the CPR target
+quotients must be defined in device tree.
+
+This document describes the MMSS LDO specific CPR4 bindings.
+
+=======================
+Required Node Structure
+=======================
+
+CPR3 regulators must be described in three levels of devices nodes. The first
+level describes the CPR3 controller. The second level describes exacly one
+hardware thread managed by the controller. The third level describes one or
+more logical regulators handled by the CPR thread.
+
+All platform independent cpr3-regulator binding guidelines defined in
+cpr3-regulator.txt also apply to cpr4-mmss-ldo-regulator devices.
+
+====================================
+First Level Nodes - CPR3 Controllers
+====================================
+
+MMSS LDO specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be the following:
+ "qcom,cpr4-sdm660-mmss-ldo-regulator".
+
+- clocks
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Array of clock tuples in which each tuple consists of a
+ phandle to a clock device and a clock ID number. The
+ following clocks must be specified: MMSS RBCPR and MMSS
+ RBCPR AHB.
+
+- clock-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Clock names. This list must match up 1-to-1 with the clocks
+ specified in the 'clocks' property. "core_clk", and "bus_clk"
+ must be specified.
+
+- qcom,cpr-step-quot-fixed
+ Usage: Optional
+ Value type: <u32>
+ Definition: Fixed step quotient value used by controller for applying
+ the SDELTA margin adjustments on the programmed target
+ quotient values. The step quotient is the number of
+ additional ring oscillator ticks observed for each
+ qcom,voltage-step increase in vdd-supply output voltage.
+ Supported values: 0 - 63.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+MMSS specific properties:
+N/A
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+MMSS specific properties:
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. This value must be 6
+ for sdm660 GFX LDO. These fuse corners are: MinSVS,
+ LowSVS, SVS, SVSP, NOM and NOMP. The open-loop voltage fuses
+ are allocated for LowSVS, SVS, NOM and NOMP corners. The
+ open-loop voltages for MinSVS and SVSP are derived by
+ applying fixed offset from LowSVS and NOM open-loop voltages
+ respectively. The closed-loop offset voltage fuses are
+ allocated for LowSVS, SVS, NOM and NOMP corners. The MinSVS
+ and SVSP corners use the closed-loop offset voltage fuses of
+ LowSVS and NOM corners respectively.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. For MMSS the maximum
+ supported value is 8. These combos correspond to CPR
+ revision fuse values from 0 to 7 in order.
+
+- qcom,mem-acc-voltage
+ Usage: required if mem-acc-supply is specified for the CPR3 controller
+ containing this CPR3 regulator
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the mem-acc-supply
+ corner for each voltage corner in order from lowest to highest.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+- qcom,cpr-target-quotients
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists. Each tuple defines the
+ CPR target quotient for each ring oscillator (RO) for a
+ given corner. Since CPR3 supports exactly 16 ROs, each
+ tuple must contain 16 elements corresponding to RO0 through
+ RO15 in order. If a given RO is unused for a corner, then
+ its target quotient should be specified as 0.
+
+ Each tuple list in the grouping must meet the same size
+ requirements as those specified for qcom,mem-acc-voltage
+ above. The tuples in a given list are ordered from the
+ lowest corner to the highest corner.
+
+- qcom,cpr-ro-scaling-factor
+ Usage: required if qcom,cpr-closed-loop-voltage-adjustment is
+ specified
+ Value type: <prop-encoded-array>
+ Definition: The common definition of this property in cpr3-regulator.txt
+ is accurate for MMSS CPR3 controllers except for this
+ modification:
+
+ Each tuple list must contain the number of tuples defined in
+ the corresponding element of the qcom,cpr-corners property
+ or the qcom,cpr-speed-bins property as opposed to the value
+ of the qcom,cpr-fuse-corners property.
+
+- qcom,cpr-fused-closed-loop-voltage-adjustment-map
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR fused
+ corner closed-loop offset adjustment fuse to utilize for
+ each voltage corner in order from lowest to highest.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bins property. A single tuple may only
+ be specified if all of the corner counts in qcom,cpr-corners
+ are the same.
+
+ Each tuple element must be either 0 or in the range 1 to
+ qcom,cpr-fuse-corners. A value of 0 signifies that no fuse
+ based adjustment should be applied to the fuse corner.
+ Values 1 to qcom,cpr-fuse-corners denote the specific fuse
+ corner that should be used by a given voltage corner.
+
+- qcom,cpr-corner-allow-ldo-mode
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the LDO mode
+ allowed state for each voltage corner in order from lowest
+ to highest. Each element in the tuple should be either
+ 0 (LDO mode not allowed) or 1 (LDO mode allowed).
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bin-corners property. A single tuple may
+ only be specified if all of the corner counts in
+ qcom,cpr-corners are the same.
+
+- qcom,cpr-corner-allow-closed-loop
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR
+ closed-loop operation allowed state for each voltage corner
+ in order from lowest to highest. Each element in the tuple
+ should be either 0 (CPR closed-loop operation not allowed)
+ or 1 (CPR closed-loop operation allowed).
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the
+ corresponding element of the qcom,cpr-corners property or
+ the qcom,cpr-speed-bin-corners property. A single tuple may
+ only be specified if all of the corner counts in
+ qcom,cpr-corners are the same.
+
+Note that the qcom,cpr-closed-loop-voltage-fuse-adjustment property is not
+meaningful for MMSS LDO CPR3 regulator nodes since target quotients are not
+defined in fuses.
+
+=======
+Example
+=======
+
+gfx_cpr: cpr4-ctrl@05061000 {
+ compatible = "qcom,cpr4-sdm660-mmss-ldo-regulator";
+ reg = <0x05061000 0x4000>, <0x00784000 0x1000>;
+ reg-names = "cpr_ctrl", "fuse_base";
+ interrupts = <GIC_SPI 285 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "cpr";
+ qcom,cpr-ctrl-name = "gfx";
+
+ qcom,cpr-sensor-time = <1000>;
+ qcom,cpr-loop-time = <5000000>;
+ qcom,cpr-idle-cycles = <15>;
+ qcom,cpr-step-quot-init-min = <8>;
+ qcom,cpr-step-quot-init-max = <12>;
+ qcom,cpr-count-mode = <0>; /* All at once */
+
+ vdd-supply = <&gfx_stub_vreg>;
+ mem-acc-supply = <&gfx_mem_acc_vreg>;
+ system-supply = <&pm660l_s3_level>; /* vdd_cx */
+ qcom,voltage-step = <5000>;
+ vdd-thread0-ldo-supply = <&gfx_ldo_vreg>;
+
+ qcom,cpr-enable;
+
+ thread@0 {
+ qcom,cpr-thread-id = <0>;
+ qcom,cpr-consecutive-up = <0>;
+ qcom,cpr-consecutive-down = <2>;
+ qcom,cpr-up-threshold = <0>;
+ qcom,cpr-down-threshold = <2>;
+
+ gfx_vreg_corner: regulator {
+ regulator-name = "gfx_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <7>;
+
+ qcom,cpr-fuse-corners = <6>;
+ qcom,cpr-fuse-combos = <8>;
+ qcom,cpr-corners = <7>;
+
+ qcom,cpr-corner-fmax-map = <1 2 3 4 5 6>;
+
+ qcom,cpr-voltage-ceiling =
+ <584000 644000 724000 788000
+ 868000 924000 1068000>;
+ qcom,cpr-voltage-floor =
+ <504000 504000 596000 652000
+ 712000 744000 1068000>;
+
+ qcom,mem-acc-voltage = <1 1 1 2 2 2 2>;
+ qcom,system-voltage =
+ <RPM_SMD_REGULATOR_LEVEL_MIN_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_LOW_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_SVS_PLUS>,
+ <RPM_SMD_REGULATOR_LEVEL_NOM>,
+ <RPM_SMD_REGULATOR_LEVEL_NOM_PLUS>,
+ <RPM_SMD_REGULATOR_LEVEL_TURBO>;
+
+ qcom,corner-frequencies =
+ <160000000 266000000 370000000
+ 465000000 588000000 647000000
+ 800000000>;
+
+ qcom,cpr-target-quotients =
+ <0 0 0 0 0 0 185 179
+ 291 299 304 319 0 0 0 0>,
+ <0 0 0 0 0 0 287 273
+ 425 426 443 453 0 0 0 0>,
+ <0 0 0 0 0 0 414 392
+ 584 576 608 612 0 0 0 0>,
+ <0 0 0 0 0 0 459 431
+ 684 644 692 679 0 0 0 0>,
+ <0 0 0 0 0 0 577 543
+ 798 768 823 810 0 0 0 0>,
+ <0 0 0 0 0 0 669 629
+ 886 864 924 911 0 0 0 0>,
+ <0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0>;
+
+ qcom,cpr-ro-scaling-factor =
+ < 0 0 0 0 0 0 2035 1917
+ 1959 2131 2246 2253 0 0 0 0>,
+ < 0 0 0 0 0 0 2035 1917
+ 1959 2131 2246 2253 0 0 0 0>,
+ < 0 0 0 0 0 0 2035 1917
+ 1959 2131 2246 2253 0 0 0 0>,
+ < 0 0 0 0 0 0 2035 1917
+ 1959 2131 2246 2253 0 0 0 0>,
+ < 0 0 0 0 0 0 2035 1917
+ 1959 2131 2246 2253 0 0 0 0>,
+ < 0 0 0 0 0 0 2035 1917
+ 1959 2131 2246 2253 0 0 0 0>,
+ < 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0>;
+
+ qcom,cpr-scaled-open-loop-voltage-as-ceiling;
+ qcom,cpr-corner-ldo-mode-allowed =
+ <1 1 1 1 1 1 0>;
+ qcom,cpr-corner-use-closed-loop =
+ <1 1 1 1 1 1 0>;
+ };
+ };
+};
diff --git a/Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt b/Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt
new file mode 100644
index 0000000..ff80035
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cprh-kbss-regulator.txt
@@ -0,0 +1,459 @@
+Qualcomm Technologies, Inc. CPRh Regulator - KBSS Specific Bindings
+
+KBSS CPRh controllers each support one CPR thread that monitors the voltage
+of a single Kryo-B CPU subystem (KBSS) cluster that is powered by a single
+regulator supply. The DCVSh block interacts with the CPRh controller for full
+hardware DCVS support.
+
+Both CPR open-loop voltages and CPR target quotients are stored in hardware
+fuses for KBSS CPRh controllers.
+
+This document describes the KBSS specific CPRh bindings.
+
+=======================
+Required Node Structure
+=======================
+
+CPRh regulators must be described in three levels of devices nodes. The first
+level describes the CPRh controller. The second level describes one hardware
+thread managed by the controller. The third level describes one regulator
+handled by the CPR thread.
+
+All platform independent cpr3-regulator binding guidelines defined in
+cpr3-regulator.txt also apply to cprh-kbss-regulator devices.
+
+====================================
+First Level Nodes - CPR3 Controllers
+====================================
+
+KBSS specific properties:
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be one of the following:
+ "qcom,cprh-msm8998-v1-kbss-regulator",
+ "qcom,cprh-msm8998-v2-kbss-regulator",
+ "qcom,cprh-msm8998-kbss-regulator",
+ "qcom,cprh-sdm660-kbss-regulator".
+ If the SoC revision is not specified, then it is assumed to
+ be the most recent revision of MSM8998, i.e. v2.
+
+- qcom,cpr-controller-id
+ Usage: required
+ Value type: <u32>
+ Definition: Identifies the controller number for subsystems that are managed
+ by multiple CPR controllers. For KBSS, the supported values are 0
+ and 1, corresponding to each cluster.
+
+- qcom,apm-threshold-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the APM threshold voltage in microvolts. The
+ floor to ceiling range for every corner is adjusted to ensure
+ it does not intersect this voltage. The value of this property
+ must match with the APM threshold voltage defined in the OSM
+ device to ensure that if the VDD_APCC supply voltage is above
+ this level, then the APM is switched to use VDD_APCC and if
+ VDD_APCC is below this level, then the APM is switched to use
+ VDD_MX.
+
+- qcom,apm-crossover-voltage
+ Usage: required if qcom,apm-threshold-voltage is specified
+ Value type: <u32>
+ Definition: Specifies the APM crossover voltage in microvolts which
+ corresponds to the voltage the VDD supply must be set at
+ during an APM switch transition.
+
+- qcom,apm-hysteresis-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the voltage in microvolts used to adjust floor
+ voltages with respect to the APM threshold voltage. This
+ voltage is used to reduce the number of corners whose floor
+ must be raised to ensure stable operation with manual APM
+ switching. If this property is not specified, then a value
+ of 0 is assumed.
+
+- qcom,mem-acc-threshold-voltage
+ Usage: optional
+ Value type: <u32>
+ Definition: Specifies the highest memory accelerator (MEM ACC) threshold
+ voltage in microvolts. The floor to ceiling voltage range
+ for every corner is adjusted to ensure that it does not
+ intersect this voltage. The value of this property must
+ match with the MEM ACC threshold voltage defined in the OSM
+ device to ensure that MEM ACC settings are switched
+ appropriately.
+
+- qcom,mem-acc-crossover-voltage
+ Usage: required if qcom,mem-acc-threshold-voltage is specified
+ Value type: <u32>
+ Definition: Specifies the MEM ACC crossover voltage in microvolts which
+ corresponds to the voltage the VDD supply must be set to
+ when switching the MEM ACC configuration.
+
+- qcom,voltage-base
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the voltage in microvolts used by the CRR controller
+ to resolve open-loop and floor voltages. In particular, this
+ voltage is added to the programmed open-loop and floor voltages
+ and it corresponds to the minimum supported setpoint of the
+ vdd-supply.
+
+- qcom,cpr-saw-use-unit-mV
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the unit used in SAW PVC
+ interface is mV. Use this for vdd-supply regulators which
+ do not use PMIC voltage control register LSBs per actually
+ unique PMIC regulator output voltage.
+
+- qcom,cpr-up-down-delay-time
+ Usage: required
+ Value type: <u32>
+ Definition: The time to delay in nanoseconds between consecutive CPR
+ measurements when the last measurement recommended
+ increasing or decreasing the vdd-supply voltage.
+
+- qcom,cpr-down-error-step-limit
+ Usage: required
+ Value type: <u32>
+ Definition: CPRh 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.
+
+- qcom,cpr-up-error-step-limit
+ Usage: required
+ Value type: <u32>
+ Definition: CPRh 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.
+
+- qcom,cpr-step-quot-fixed
+ Usage: optional
+ Value type: <u32>
+ Definition: Fixed step quotient value used by controller for applying
+ the SDELTA margin adjustments on the programmed target
+ quotient values. The step quotient is the number of
+ additional ring oscillator ticks observed for each
+ qcom,voltage-step increase in vdd-supply output voltage.
+ Supported values: 0 - 63.
+
+- qcom,cpr-voltage-settling-time
+ Usage: optional
+ Value type: <u32>
+ The time in nanoseconds that it takes for the vdd-supply
+ voltage to settle after being increased or decreased by
+ qcom,voltage-step microvolts. This is used as the wait
+ time after applying SDELTA voltage margin adjustments.
+
+- qcom,cpr-corner-switch-delay-time
+ Usage: optional
+ Value type: <u32>
+ The time in nanoseconds that the CPR controller must delay
+ to allow voltage settling per 1 mV of voltage change after a
+ corner change.
+
+- qcom,cpr-hw-closed-loop
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that the KBSS CPRh controller
+ should operate in hardware closed-loop mode as opposed to
+ open-loop.
+
+- qcom,cpr-temp-point-map
+ Usage: required if qcom,corner-band-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <prop-encoded-array>
+ Definition: The temperature points in decidegrees Celsius which indicate
+ the range of temperature bands supported. If t1, t2, and t3
+ are the temperature points, then the temperature bands are:
+ (-inf, t1], (t1, t2], (t2, t3], and (t3, inf). A maximum of
+ three temperature points can be specified to define a total
+ of four different temperature bands.
+
+- qcom,cpr-initial-temp-band
+ Usage: required if qcom,corner-band-allow-temp-adjustment is specified
+ for at least one of the CPR3 regulators.
+ Value type: <u32>
+ Definition: The initial temp band considering 0-based index at which
+ the baseline target quotients are derived and fused.
+
+=================================================
+Second Level Nodes - CPR Threads for a Controller
+=================================================
+
+KBSS specific properties:
+N/A
+
+===============================================
+Third Level Nodes - CPR Regulators for a Thread
+===============================================
+
+KBSS specific properties:
+- qcom,cpr-fuse-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse corners. This value must be 4
+ for KBSS. These fuse corners are: LowSVS, SVS, Nominal,
+ and Turbo.
+
+- qcom,cpr-fuse-combos
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of fuse combinations being supported by
+ the device. This value is utilized by several other
+ properties. Supported values are 1 up to the maximum
+ possible for a given regulator type. For KBSS the maximum
+ supported value is 8. These combos correspond to CPR
+ revision fuse values 0 to 7 in order.
+
+- qcom,allow-quotient-interpolation
+ Usage: optional
+ Value type: <empty>
+ Definition: Boolean flag which indicates that it is acceptable to use
+ interpolated CPR target quotient values. These values are
+ interpolated between the target quotient Fmax fuse values.
+
+- qcom,cpr-corner-bands
+ Usage: required if qcom,corner-band-allow-core-count-adjustment
+ or qcom,corner-band-allow-temp-adjustment is specified
+ for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A list of integers which defines how many corner bands
+ exist for each fuse combination. Supported values are 1 to 4.
+ The list must contain either qcom,cpr-fuse-combos number of
+ elements in which case the corner band counts are applied to
+ fuse combinations 1-to-1 or the list must contain exactly 1
+ element which is used regardless of the fuse combination
+ found on a given chip.
+
+- qcom,cpr-speed-bin-corner-bands
+ Usage: required if qcom,cpr-speed-bins and
+ qcom,corner-band-allow-core-count-adjustment or
+ qcom,corner-band-allow-temp-adjustment are specified for
+ this CPR3 regulator.
+ Value type: <prop-encded-array>
+ Definition: A list of integers which defines how many corner bands
+ are to be used for each speed bin. The list must contain
+ qcom,cpr-speed-bins number of elements.
+
+- qcom,corner-band-allow-core-count-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the CPR core
+ count adjustment feature enable state for each corner band
+ in order from lowest to highest. Each element in the tuple
+ should be either 0 (per-core-count adjustment not allowed)
+ or 1 (per-core-count adjustment allowed). A maximum of four
+ corner bands may be used to partition the corner space into
+ contiguous corner ranges. For all corners within a corner band,
+ the same per-core-count adjustments are applied.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the corresponding
+ element of the qcom,cpr-corner-bands property.
+
+- qcom,max-core-count
+ Usage: required if qcom,corner-band-allow-core-count-adjustment is
+ specified for this CPR3 regulator.
+ Value type: <u32>
+ Definition: The maximum number of cores considered for core-count vmin
+ adjustments specified for this regulator's corner bands.
+
+- qcom,corner-band-allow-temp-adjustment
+ Usage: optional
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which each define the temperature
+ adjustment feature enable state for each corner band
+ in order from lowest to highest. Each element in the tuple
+ should be either 0 (temperature adjustment not allowed)
+ or 1 (temperature adjustment allowed). A maximum of four
+ corner bands may be used to partition the corner space into
+ contiguous corner ranges. For all corners within a corner band,
+ the same temperature adjustments are applied.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the corresponding
+ element of the qcom,cpr-corner-bands property.
+
+- qcom,cpr-corner-band-map
+ Usage: required if qcom,corner-band-allow-core-count-adjustment
+ or qcom,corner-band-allow-temp-adjustment is specified
+ for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A list of integer tuples which correspond to corner numbers
+ and define the corner bands to be used for temperature or
+ per-core-count adjustments. The corner numbers must be specified
+ in increasing order to result in partitioning the corner space
+ into contiguous corner ranges. The supported tuple size is 1
+ to 4 elements. For example, a tuple with corners defined as
+ c1, c2, c3, c4 results in the following corner band mapping:
+
+ [c1, c2) -> Corner Band 1
+ [c2, c3) -> Corner Band 2
+ [c3, c4) -> Corner Band 3
+ [c4, inf)-> Corner Band 4
+
+ Corners less than c1 will have no per-core-count or temperature
+ adjustments. Adjustments associated with each corner band X are
+ defined in the corresponding
+ qcom,cpr-corner-bandX-temp-core-voltage-adjustment property.
+
+ The list must contain qcom,cpr-fuse-combos number of tuples
+ in which case the tuples are matched to fuse combinations
+ 1-to-1 or qcom,cpr-speed-bins number of tuples in which case
+ the tuples are matched to speed bins 1-to-1 or exactly 1
+ tuple which is used regardless of the fuse combination and
+ speed bin found on a given chip.
+
+ Each tuple must be of the length defined in the corresponding
+ element of the qcom,cpr-corner-bands property.
+
+- qcom,cpr-corner-bandX-temp-core-voltage-adjustment
+ Usage: required if qcom,corner-band-allow-core-count-adjustment
+ is specified for this CPR3 regulator.
+ Value type: <prop-encoded-array>
+ Definition: A grouping of integer tuple lists for corner bandX. The possible
+ values for X are 1 to 4. Each tuple defines the temperature based
+ voltage adjustment in microvolts for each temperature band
+ from lowest to highest for a given number of online cores. Each
+ tuple must have a number of elements equal to either (the number
+ of elements in qcom,cpr-temp-point-map + 1), if
+ qcom,cpr-temp-point-map is specified, or 1.
+
+ Each tuple list must contain a number of tuples equal to
+ either qcom,max-core-count, if qcom,max-core-count is
+ specified, or 1. The tuples should be ordered from lowest
+ to highest core count.
+
+ The tuple list grouping must contain qcom,cpr-fuse-combos
+ number of tuple lists in which case the lists are matched to
+ fuse combinations 1-to-1 or qcom,cpr-speed-bins number of
+ tuple lists in which case the lists are matched to
+ speed bins 1-to-1 or exactly 1 list which is used regardless
+ of the fuse combination and speed bin found on a given chip.
+
+=======
+Example
+=======
+
+apc0_cpr: cprh-ctrl@179c8000 {
+ compatible = "qcom,cprh-msm8998-kbss-regulator";
+ reg = <0x179c8000 0x4000>, <0x00784000 0x1000>;
+ reg-names = "cpr_ctrl", "fuse_base";
+ clocks = <&clock_gcc clk_gcc_hmss_rbcpr_clk>;
+ clock-names = "core_clk";
+ qcom,cpr-ctrl-name = "apc0";
+ qcom,cpr-controller-id = <0>;
+
+ qcom,cpr-sensor-time = <1000>;
+ qcom,cpr-loop-time = <5000000>;
+ qcom,cpr-idle-cycles = <15>;
+ qcom,cpr-up-down-delay-time = <3000>;
+ qcom,cpr-step-quot-init-min = <11>;
+ qcom,cpr-step-quot-init-max = <13>;
+ qcom,cpr-count-mode = <2>; /* Staggered */
+ qcom,cpr-down-error-step-limit = <1>;
+ qcom,cpr-up-error-step-limit = <1>;
+ qcom,cpr-corner-switch-delay-time = <1600>;
+ qcom,cpr-voltage-settling-time = <1600>;
+
+ qcom,apm-threshold-voltage = <800000>;
+ qcom,apm-hysteresis-voltage = <4000>;
+ qcom,voltage-step = <4000>;
+ qcom,voltage-base = <352000>;
+ qcom,cpr-saw-use-unit-mV;
+
+ qcom,cpr-enable;
+ qcom,cpr-hw-closed-loop;
+
+ qcom,cpr-initial-temp-band = <3>;
+ qcom,cpr-temp-point-map = <0 25 85>;
+
+ thread@0 {
+ qcom,cpr-thread-id = <0>;
+ qcom,cpr-consecutive-up = <2>;
+ qcom,cpr-consecutive-down = <2>;
+ qcom,cpr-up-threshold = <0>;
+ qcom,cpr-down-threshold = <0>;
+
+ apc0_pwrcl_vreg: regulator-pwrcl {
+ regulator-name = "apc0_pwrcl_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <24>;
+
+ qcom,cpr-fuse-corners = <4>;
+ qcom,cpr-fuse-combos = <1>;
+ qcom,cpr-corners = <23>;
+
+ qcom,cpr-corner-fmax-map = <7 10 17 23>;
+
+ qcom,cpr-voltage-ceiling =
+ <632000 632000 632000 632000 632000
+ 632000 632000 700000 700000 700000
+ 828000 828000 828000 828000 828000
+ 828000 828000 1024000 1024000 1024000
+ 1024000 1024000 1024000>;
+
+ qcom,cpr-voltage-floor =
+ <572000 572000 572000 572000 572000
+ 572000 572000 568000 568000 568000
+ 684000 684000 684000 684000 684000
+ 684000 684000 856000 856000 856000
+ 856000 856000 856000>;
+
+ qcom,corner-frequencies =
+ <300000000 345600000 422400000
+ 499200000 576000000 633600000
+ 710400000 806400000 883200000
+ 960000000 1036800000 1113600000
+ 1190400000 1248000000 1324800000
+ 1401600000 1478400000 1497600000
+ 1574400000 1651200000 1728000000
+ 1804800000 1881600000>;
+ };
+
+ qcom,cpr-corner-bands = <4>;
+ qcom,corner-band-allow-core-count-adjustment = <1 1 1 1>;
+ qcom,corner-band-allow-temp-adjustment = <1 0 0 0>;
+ qcom,cpr-corner-band-map = <7 14 18 20>;
+ qcom,max-core-count = <4>;
+ qcom,cpr-corner-band1-temp-core-voltage-adjustment =
+ <(-24000) (-20000) (-20000) (-16000)>,
+ <(-16000) (-16000) (-12000) (-8000)>,
+ <(-8000) (-8000) (-4000) (-4000)>,
+ <0 0 0 0>;
+ qcom,cpr-corner-band2-temp-core-voltage-adjustment =
+ <(-36000) 0 0 0>,
+ <(-32000) 0 0 0>,
+ <(-24000) 0 0 0>,
+ < 0 0 0 0>;
+ qcom,cpr-corner-band3-temp-core-voltage-adjustment =
+ <(-40000) 0 0 0>,
+ <(-36000) 0 0 0>,
+ <(-32000) 0 0 0>,
+ < 0 0 0 0>;
+ qcom,cpr-corner-band4-temp-core-voltage-adjustment =
+ <(-44000) 0 0 0>,
+ <(-32000) 0 0 0>,
+ <(-24000) 0 0 0>,
+ < 0 0 0 0>;
+ };
+ };
+}
diff --git a/Documentation/devicetree/bindings/regulator/kryo-regulator.txt b/Documentation/devicetree/bindings/regulator/kryo-regulator.txt
new file mode 100644
index 0000000..ad630ee
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/kryo-regulator.txt
@@ -0,0 +1,151 @@
+Qualcomm Technologies, Inc. Kryo Regulator
+
+The Kryo regulator device is designed for QTI's application processor cores
+that can draw power from a common power rail via a block head switch (BHS)
+or from a configurable LDO when certain power constraints are met. By using
+Kryo regulators, the CPU subsystem is capable of selecting LDO or BHS modes
+for each cluster.
+
+=================
+First Level Nodes
+=================
+
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: must be "qcom,kryo-regulator"
+
+- reg
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Specifies addresses and sizes for the memory mapped regions of the
+ power gate and LDO registers per cluster and per shared power rail
+ domain. The third address must correspond to the register space
+ containing the CPU subsystem HW revision register.
+
+- reg-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Identifies the reg property entries. Must contain the following
+ strings: "pm-apc", "pm-apcc", and "apcs-csr".
+
+- qcom,ldo-default-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: The default value for LDO voltage in microvolts. Must be
+ between 520000 uV and 865000 uV.
+
+- qcom,retention-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: The value for retention voltage in microvolts. Must be
+ between 520000 and 865000 uV.
+
+- qcom,ldo-headroom-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: Voltage in microvolts required between the VDD_APCC voltage supply
+ and the LDO output in order for the LDO to be operational.
+
+- qcom,vref-functional-step-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: The voltage change in microvolts for each step in the
+ functional LDO set point.
+
+- qcom,vref-functional-min-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: The minimum configurable functional LDO voltage in microvolts.
+
+- qcom,vref-retention-step-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: The voltage change in microvolts for each step in the
+ retention LDO set point.
+
+- qcom,vref-retention-min-voltage
+ Usage: required
+ Value type: <u32>
+ Definition: The minimum configurable retention LDO voltage in microvolts.
+
+- qcom,ldo-config-init
+ Usage: required
+ Value type: <u32>
+ Definition: Initialization value used to configure the Kryo LDO hardware.
+
+- qcom,apm-config-init
+ Usage: required
+ Value type: <u32>
+ Definition: Initialization value used to configure the Kryo APM hardware.
+
+- qcom,cluster-num
+ Usage: required
+ Value type: <u32>
+ Definition: Specifies the number of the cluster this regulator controls.
+
+==================
+Second Level Nodes
+==================
+The second level node represents a regulator which enables control of LDO retention
+mode per Kryo regulator device. This second level node is required.
+
+The following regulator framework properties must be specified for both first and
+second level nodes: regulator-name, regulator-min-microvolt, and regulator-max-microvolt.
+
+Additional core regulator framework properties may also be used. For a full list of
+supported bindings refer to Documentation/devicetree/bindings/regulator/regulator.txt.
+
+=======
+Example
+=======
+
+ kryo0_vreg: regulator@99a2000 {
+ compatible = "qcom,kryo-regulator";
+ regulator-name = "kryo0";
+ reg = <0x99a2000 0x1000>, <0x99e0000 0x1000>,
+ <0x9820000 0x1000>;
+ reg-names = "pm-apc", "pm-apcc", "apcs-csr";
+ regulator-min-microvolt = <520000>;
+ regulator-max-microvolt = <865000>;
+ qcom,ldo-default-voltage = <750000>;
+ qcom,retention-voltage = <520000>;
+ qcom,ldo-headroom-voltage = <150000>;
+ qcom,vref-functional-step-voltage = <4100>;
+ qcom,vref-functional-min-voltage = <299000>;
+ qcom,vref-retention-step-voltage = <4554>;
+ qcom,vref-retention-min-voltage = <332000>;
+ qcom,ldo-config-init = <0xf1f0e471>;
+ qcom,apm-config-init = <0x0>;
+ qcom,cluster-num = <0>;
+ kryo0_retention_vreg: regulator {
+ regulator-name = "kryo0-retention";
+ regulator-min-microvolt = <332000>;
+ regulator-max-microvolt = <865000>;
+ };
+ };
+
+ kryo1_vreg: regulator@99d2000 {
+ compatible = "qcom,kryo-regulator";
+ regulator-name = "kryo1";
+ reg = <0x99d2000 0x1000>, <0x99e0000 0x1000>,
+ <0x9820000 0x1000>;
+ reg-names = "pm-apc", "pm-apcc", "apcs-csr";
+ regulator-min-microvolt = <520000>;
+ regulator-max-microvolt = <865000>;
+ qcom,ldo-default-voltage = <750000>;
+ qcom,retention-voltage = <520000>;
+ qcom,ldo-headroom-voltage = <150000>;
+ qcom,vref-functional-step-voltage = <4063>;
+ qcom,vref-functional-min-voltage = <296000>;
+ qcom,vref-retention-step-voltage = <4554>;
+ qcom,vref-retention-min-voltage = <332000>;
+ qcom,ldo-config-init = <0xf1f0e471>;
+ qcom,apm-config-init = <0x0>;
+ qcom,cluster-num = <1>;
+ kryo1_retention_vreg: regulator {
+ regulator-name = "kryo1-retention";
+ regulator-min-microvolt = <332000>;
+ regulator-max-microvolt = <865000>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/regulator/mem-acc-regulator.txt b/Documentation/devicetree/bindings/regulator/mem-acc-regulator.txt
new file mode 100644
index 0000000..5515457
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/mem-acc-regulator.txt
@@ -0,0 +1,266 @@
+Qualcomm Technologies, Inc. Memory Accelerator
+
+Memory accelerator configures the power-mode (corner) for the
+accelerator.
+
+Required properties:
+- compatible: Must be "qcom,mem-acc-regulator"
+- regulator-name: A string used to describe the regulator
+- regulator-min-microvolt: Minimum corner value as min constraint, which
+ should be 1 for SVS corner
+- regulator-max-microvolt: Maximum corner value as max constraint, which
+ should be 4 for SUPER_TURBO or 3 for TURBO
+
+Optional properties:
+- reg: Register addresses for acc-sel-l1, acc-sel-l2 control, acc-en,
+ MEM ACC eFuse address, acc-l1-custom , acc-l2-custom,
+ mem-acc-type1, mem-acc-type2, mem-acc-type3, mem-acc-type4,
+ mem-acc-type5 and mem-acc-type6.
+- reg-names: Register names. Must be "acc-sel-l1",
+ "acc-sel-l2", "acc-en", "efuse_addr",
+ "acc-l1-custom", "acc-l2-custom", "mem-acc-type1",
+ "mem-acc-type2", "mem-acc-type3", "mem-acc-type4",
+ "mem-acc-type5", "mem-acc-type6".
+ A given mem-acc-regulator driver must have "acc-sel-l1" or
+ "acc-sel-l2" or "mem-acc-type*" reg-names property and
+ related register address property.
+- qcom,corner-acc-map Array which maps the APC (application processor)
+ corner value to the accelerator corner. The number of elements
+ in this property defines the number of accelerator corners.
+ Either qcom,corner-acc-map property or qcom,cornerX-reg-config
+ properties should be specified.
+- qcom,acc-en-bit-pos Array which specifies bit positions in the
+ 'acc-en' register. Setting these bits forces the
+ the acclerator to use the corner value specified
+ in the 'acc-sel-l1' and 'acc-sel-l2' register.
+- qcom,acc-sel-l1-bit-size Integer which specifies the number of bits in
+ the 'acc-sel-l1' register which define each L1
+ select parameter. If this property is not
+ specified, then a default value of 2 is assumed.
+- qcom,acc-sel-l1-bit-pos Array which specifies bit positions in the
+ 'acc-sel-l1' register. Each element in this array
+ is the LSB of an N-bit value where 'N' is
+ defined by the qcom,acc-sel-l1-bit-size
+ property. This N-bit value specifies the corner
+ value used by the accelerator for the L1 cache.
+- qcom,acc-sel-l2-bit-size Integer which specifies the number of bits in
+ the 'acc-sel-l2' register which define each L2
+ select parameter. If this property is not
+ specified, then a default value of 2 is assumed.
+- qcom,acc-sel-l2-bit-pos Array which specifies bit positions in the
+ 'acc-sel-l2' register. Each element in this array
+ is the LSB of an N-bit value where 'N' is
+ defined by the qcom,acc-sel-l2-bit-size
+ property. This N-bit value specifies the corner
+ value used by the accelerator for the L2 cache.
+- qcom,l1-acc-custom-data: Array which maps APC corner values to L1 ACC custom data values.
+ The corresponding custom data is written into the custom register
+ while switching between APC corners. The custom register address
+ is specified by "acc-11-custom" reg-property. The length of the array
+ should be equal to number of APC corners.
+- qcom,l2-acc-custom-data: Array which maps APC corner values to L2 ACC custom data values.
+ The corresponding custom data is written into the custom register
+ while switching between APC corners. The custom register address
+ is specified by "acc-l2-custom" reg-property. The length of the array
+ should be equal to number of APC corners.
+- qcom,override-acc-fuse-sel: Array of 4 elements which specify the way to read the override fuse.
+ The override fuse value is used by the qcom,override-fuse-version-map
+ to identify the correct set of override properties.
+ The 4 elements with index [0..4] are:
+ [0] => the fuse row number of the selector
+ [1] => LSB bit position of the bits
+ [2] => number of bits
+ [3] => fuse reading method, 0 for direct reading or 1 for SCM reading
+- qcom,override-fuse-version-map: Array of integers which each match to a override fuse value.
+ Any element in a tuple may use the value 0xffffffff as a wildcard.
+ The index of the first value (in the array) which matches the override fuse
+ is used to select the right tuples from the other override properties.
+- qcom,override-corner-acc-map: Array of tuples which overrides the existing acc-corner map
+ (specified by qcom,corner-acc-map) with corner values selected
+ from this property. "qcom,override-corner-acc-map" must contain the
+ same number of tuples as "qcom,override-fuse-version-map". These tuples
+ are then mapped one-to-one in the order specified. If the
+ "qcom,override-fuse-version-map" property is not specified, then
+ "qcom,override-corner-acc-map" must contain a single tuple which is then
+ applied unconditionally.
+- qcom,override-l1-acc-custom-data: Array of tuples of which overrides the existing l1-acc-custom data
+ (specified by qcom,l1-acc-custom-data), with values specified in this property.
+ The corresponding custom data is written into the custom register while
+ switching between APC corners. The custom register address is specified by
+ "acc-11-custom" reg-property. This property can only be specified if the
+ "qcom,l1-acc-custom-data" is already defined. If the
+ "qcom,override-fuse-version-map" property is specified, then
+ qcom,override-l1-acc-custom-data must contain the same number of tuples
+ as "qcom,override-fuse-version-map". These tuples are then mapped one-to-one
+ in the order specified. If the qcom,override-fuse-version-map property is
+ not specified, then "qcom,override-l1-acc-custom-data" must contain a single
+ tuple which is then applied unconditionally.
+- qcom,override-l2-acc-custom-data: Array of tuples of which overrides the existing l1-acc-custom data
+ (specified by qcom,l2-acc-custom-data), with values specified in this property.
+ The corresponding custom data is written into the custom register while
+ switching between APC corners. The custom register address is specified by
+ "acc-12-custom" reg-property. This property can only be specified if the
+ "qcom,l2-acc-custom-data" is already defined. If the
+ "qcom,override-fuse-version-map" property is specified, then
+ "qcom,override-l2-acc-custom-data" must contain the same number of tuples
+ as "qcom,override-fuse-version-map". These tuples are then mapped one-to-one
+ in the order specified. If the qcom,override-fuse-version-map property is
+ not specified, then "qcom,override-l2-acc-custom-data" must contain a single
+ tuple which is then applied unconditionally.
+- qcom,mem-acc-type1: Array which specifies the value to be written to the mem acc type1 register for each fuse
+ corner, from the lowest fuse corner to the highest fuse corner. The length of the array
+ must be equal to the number of APC fuse corners. This property must be present if reg names
+ specifies mem-acc-type1.
+- qcom,mem-acc-type2: Same as qcom,mem-acc-type1 except for mem acc type2 register.
+- qcom,mem-acc-type3: Same as qcom,mem-acc-type1 except for mem acc type3 register.
+- qcom,mem-acc-type4: Same as qcom,mem-acc-type1 except for mem acc type4 register.
+- qcom,mem-acc-type5: Same as qcom,mem-acc-type1 except for mem acc type5 register.
+- qcom,mem-acc-type6: Same as qcom,mem-acc-type1 except for mem acc type6 register.
+- qcom,acc-reg-addr-list: Array of register addresses which need to be programmed during any corner switch.
+ This property can be used when multi register configuration is needed during a corner switch.
+- qcom,acc-init-reg-config: Array of tuples specify the multi register configuration sequence need to be programmed
+ one time during device boot.
+ The format of each tuple as below:
+ <register-address-index, value>
+ Where register-address-index is used as an index in to qcom,acc-reg-addr-list property
+ to get the required register address and the value is programmed in to the corresponding
+ mapped register address. This property is required if qcom,acc-corner-addr-val-map
+ property specified.
+- qcom,cornerX-reg-config: Array of tuples specify the multi register configuration sequence need to be programmed
+ when switching from acc corner X to any other corner. The possible values for X are {1, N},
+ where N is the value defined in qcom,num-acc-corners.
+ The format of each tuple as below:
+ <register-address-index, value>
+ Where register-address-index is used as an index in to qcom,acc-reg-addr-list property
+ to get the required register address and the value is programmed in to the corresponding
+ mapped register address. Same index can be used multiple times when the register is
+ required to configure multiple times with different values in the sequence.
+ The number of register configuration sequences should be equal to N, where N is the
+ value specified in qcom,num-acc-corners property. Also, the number of tuples in each
+ register configuration sequence should be same and must be equal to the maximum required
+ register configurations in any sequence. The invalid register configuration can be
+ specified as <(-1) (-1)>. This property can only be specified when qcom,acc-corner-addr-val-map
+ property already defined. Either this property or qcom,corner-acc-map should be specified.
+- qcom,num-acc-corners: The number of acc corners supported. This property is required if qcom,cornerX-reg-config
+ property specified.
+- qcom,boot-acc-corner: The acc corner used during device boot. This property is required if qcom,cornerX-reg-config
+ property specified.
+- qcom,override-cornerX-reg-config: A grouping of register configuration sequence lists. Each list is same as
+ the qcom,cornerX-reg-config property. The possible values for X are {1, N} where N is
+ the value defined in qcom,num-acc-corners. This property is used to specify the different
+ register configuration sequence lists and select one list among them based on the selected
+ index in qcom,override-fuse-version-map property. The selected list overrides the existing
+ register configuration sequence list specified in "qcom,cornerX-reg-config". If the
+ "qcom,override-fuse-version-map" property is specified, then
+ "qcom,override-cornerX-reg-config" must contain the same number of register
+ configuration sequence lists as the number of tuples in "qcom,override-fuse-version-map".
+ These register configuration sequence lists are then mapped one-to-one
+ in the order specified. If the qcom,override-fuse-version-map property is
+ not specified, then "qcom,override-cornerX-reg-config" must contain a single
+ register configuration sequence list which is then applied unconditionally.
+ This property can only be specified if qcom,cornerX-reg-config property is already defined.
+
+mem_acc_vreg_corner: regulator@fd4aa044 {
+ compatible = "qcom,mem-acc-regulator";
+ reg = <0xfd4aa048 0x1>, <0xfd4aa044 0x1>, <0xfd4af000 0x1>,
+ <0x58000 0x1000>, <0x01942124 0x4>, <0xf900d084 1>,
+ <0xf900d088 1>, <0xf900d08c 1>, <0xf900d090 1>;
+ reg-names = "acc-en", "acc-sel-l1" , "acc-sel-l2",
+ "efuse_addr", "acc-l2-custom", "mem-acc-type1",
+ "mem-acc-type2", "mem-acc-type3", "mem-acc-type4";
+ regulator-name = "mem_acc_corner";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <3>;
+
+ qcom,acc-en-bit-pos = <0>;
+ qcom,acc-sel-l1-bit-pos = <0>;
+ qcom,acc-sel-l2-bit-pos = <0>;
+ qcom,acc-sel-l1-bit-size = <2>;
+ qcom,acc-sel-l2-bit-size = <2>;
+ qcom,corner-acc-map = <0 1 3>;
+ qcom,l2-acc-custom-data = <0x0 0x3000 0x3000>;
+
+ qcom,override-acc-fuse-sel = <0 52 2 0>;
+ qcom,override-fuse-version-map = <0>,
+ <2>,
+ <(-1)>;
+ qcom,override-corner-acc-map = <0 0 1>,
+ <0 1 2>,
+ <0 1 1>;
+ qcom,overide-l2-acc-custom-data = <0x0 0x0 0x3000>,
+ <0x0 0x3000 0x3000>,
+ <0x0 0x0 0x0>;
+ qcom,mem-acc-type1 = <0x02 0x02 0x00>;
+ qcom,mem-acc-type2 = <0x02 0x02 0x00>;
+ qcom,mem-acc-type3 = <0x02 0x02 0x00>;
+ qcom,mem-acc-type4 = <0x02 0x02 0x00>;
+
+ qcom,acc-reg-addr-list = <0x01942130 0x01942124 0x01942120>;
+ qcom,acc-init-reg-config = <1 0x55> <2 0x02>;
+
+ qcom,num-acc-corners = <3>;
+ qcom,boot-acc-corner = <2>;
+ qcom,corner1-reg-config =
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 1 -> 1 */
+ < 1 0x155>, < 2 0x0>, < 3 0x155>, /* 1 -> 2 */
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>; /* 1 -> 3 */
+
+ qcom,corner2-reg-config =
+ < 1 0x155>, <(-1) (-1)>, <(-1) (-1)>, /* 2 -> 1 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 2 -> 2 */
+ < 1 0x155>, < 2 0x0>, < 3 0x155>; /* 2 -> 3 */
+
+ qcom,corner3-reg-config =
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>, /* 3 -> 1 */
+ < 1 0x155>, <(-1) (-1)>, <(-1) (-1)>, /* 3 -> 2 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>; /* 3 -> 3 */
+
+ qcom,override-corner1-reg-config =
+ /* 1st fuse version tuple matched */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 1 -> 1 */
+ < 1 0x155>, < 2 0x0>, < 3 0x155>, /* 1 -> 2 */
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>, /* 1 -> 3 */
+
+ /* 2nd fuse version tuple matched */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 1 -> 1 */
+ < 1 0x155>, < 2 0x0>, < 3 0x155>, /* 1 -> 2 */
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>, /* 1 -> 3 */
+
+ /* 3rd fuse version tuple matched */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 1 -> 1 */
+ < 1 0x155>, < 3 0x22>, < 3 0x155>, /* 1 -> 2 */
+ < 1 0x0>, < 2 0x155>, < 3 0x144>; /* 1 -> 3 */
+
+ qcom,override-corner2-reg-config =
+ /* 1st fuse version tuple matched */
+ < 1 0x144>, < 1 0x11>, <(-1) (-1)>, /* 2 -> 1 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 2 -> 2 */
+ < 1 0x155>, < 2 0x0>, < 3 0x155>, /* 2 -> 3 */
+
+ /* 2nd fuse version tuple matched */
+ < 1 0x144>, < 2 0x133>, <(-1) (-1)>, /* 2 -> 1 */
+ <(-1) (-1)>, < 1 0x33>, <(-1) (-1)>, /* 2 -> 2 */
+ < 1 0x133>, < 2 0x0>, < 3 0x155>, /* 2 -> 3 */
+
+ /* 3rd fuse version tuple matched */
+ < 1 0x144>, < 1 0x11>, <(-1) (-1)>, /* 2 -> 1 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 2 -> 2 */
+ < 1 0x155>, < 2 0x22>, < 3 0x155>; /* 2 -> 3 */
+
+
+ qcom,override-corner3-reg-config =
+ /* 1st fuse version tuple matched */
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>, /* 3 -> 1 */
+ < 1 0x155>, <(-1) (-1)>, <(-1) (-1)>, /* 3 -> 2 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 3 -> 3 */
+
+ /* 2nd fuse version tuple matched */
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>, /* 3 -> 1 */
+ < 1 0x155>, <(-1) (-1)>, <(-1) (-1)>, /* 3 -> 2 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>, /* 3 -> 3 */
+
+ /* 3rd fuse version tuple matched */
+ < 1 0x0>, < 2 0x155>, <(-1) (-1)>, /* 3 -> 1 */
+ < 1 0x155>, < 3 0x11>, <(-1) (-1)>, /* 3 -> 2 */
+ <(-1) (-1)>, <(-1) (-1)>, <(-1) (-1)>; /* 3 -> 3 */
+};
diff --git a/Documentation/devicetree/bindings/regulator/msm_gfx_ldo.txt b/Documentation/devicetree/bindings/regulator/msm_gfx_ldo.txt
new file mode 100644
index 0000000..dcbb120
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/msm_gfx_ldo.txt
@@ -0,0 +1,148 @@
+Qualcomm Technologies, Inc. GFX LDO for Graphics
+
+The GPU core on MSM 8953 can be powered by an internal (on-die)
+MSM LDO or BHS based on its operating corner.
+
+This document describes the bindings that apply for the GFX LDO regulator.
+
+- compatible
+ Usage: required
+ Value type: <string>
+ Definition: should be "qcom,msm8953-gfx-ldo" for MSM8953 and
+ "qcom,sdm660-gfx-ldo" for SDM660
+
+- reg
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Addresses and sizes for the memory of the GFX ldo
+
+- reg-names
+ Usage: required
+ Value type: <stringlist>
+ Definition: Address names. "ldo_addr", "efuse_addr". Must be
+ specified in the same order as the corresponding addresses
+ are specified in the reg property.
+
+- regulator-name
+ Usage: required
+ Value type: <string>
+ Definition: A string used to describe the regulator.
+
+- regulator-min-microvolt
+ Usage: required
+ Value type: <u32>
+ Definition: Minimum corner value which should be 1 to represent the
+ lowest supported corner.
+
+- regulator-max-microvolt
+ Usage: required
+ Value type: <u32>
+ Definition: Maximum corner value which should be equal to qcom,num-corners.
+
+- qcom,num-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Number of voltage corners present. Many other
+ properties are sized based upon this value.
+
+- qcom,num-ldo-corners
+ Usage: required
+ Value type: <u32>
+ Definition: Number of voltage corners defined for the ldo. It is a
+ subset of qcom,num-corners (i.e. 1 to qcom,num-ldo-corners
+ are the corners for ldo operation)
+
+- qcom,init-corner
+ Usage: required
+ Value type: <u32>
+ Definition: The initial-corner at which the GFX rail is powered on.
+
+- qcom,ldo-enable-corner-map
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Integer values ( / 0) which indicate the GFX corners in which
+ ldo is to enabled. The length of this property
+ should be equal to qcom,num-corners.
+
+- qcom,ldo-voltage-ceiling
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Array of ceiling voltages in microvolts for voltage corners
+ ordered from lowest voltage corner to highest voltage corner.
+ This property must be of length defined by qcom,num-ldo-corners.
+
+- qcom,ldo-voltage-floor
+ Usage: required
+ Value type: <prop-encoded-array>
+ Definition: Array of floor voltages in microvolts for voltage corners
+ ordered from lowest voltage corner to highest voltage corner.
+ This property must be of length defined by qcom,num-ldo-corners.
+
+- vdd-cx-supply
+ Usage: optional
+ Value type: <phandle>
+ Definition: Parent regulator supply to the ldo.
+
+- qcom,vdd-cx-corner-map
+ Usage: required if vdd-cx-supply is specified.
+ Value type: <prop-encoded-array>
+ Definition: Array of integers which define the mapping of the VDD_CX corner
+ to the corresponding GFX voltage corner. The elements in
+ the array are ordered from lowest voltage corner to highest
+ voltage corner. The length of this property must be equal to
+ the value defined by qcom,num-corners.
+- mem-acc-supply
+ Usage: optional
+ Value type: <phandle>
+ Definition: Regulator to vote for the memory accelerator configuration.
+ Not Present: memory accelerator configuration not supported.
+
+- qcom,mem-acc-corner-map:
+ Usage: optional
+ Value type: <prop-encoded-aray>
+ Definition: Array of integer which defines the mapping from mem-acc
+ corner value for each gfx corner. The elements in the array
+ are ordered from lowest voltage corner to highest voltage corner.
+
+- qcom,ldo-init-voltage-adjustment
+ Usage: optional
+ Value type: <prop-encoded-aray>
+ Definition: Array of voltages in microvolts which indicate the static
+ adjustment to be applied to the open-loop voltages for the
+ LDO supported corners. The length of this property must be
+ equal to qcom,num-ldo-corners.
+
+=======
+Example
+=======
+
+ gfx_vreg_corner: ldo@0185f000 {
+ compatible = "qcom,msm8953-gfx-ldo";
+ reg = <0x0185f000 0x30>, <0xa0000 0x1000>;
+ reg-names = "ldo_addr", "efuse_addr";
+
+ regulator-name = "msm_gfx_ldo";
+ regulator-min-microvolt = <1>;
+ regulator-max-microvolt = <7>;
+
+ qcom,ldo-voltage-ceiling = <500000 700000 900000>;
+ qcom,ldo-voltage-floor = <400000 600000 800000>;
+
+ qcom,num-corners = <7>;
+ qcom,num-ldo-corners = <3>;
+ qcom,ldo-enable-corner-map = <1 1 1 0 0 0 0>;
+ qcom,init-corner = <5>;
+
+ vdd-cx-supply = <&pm8953_s2_level>;
+ qcom,vdd-cx-corner-map = <RPM_SMD_REGULATOR_LEVEL_LOW_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_LOW_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_LOW_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_SVS>,
+ <RPM_SMD_REGULATOR_LEVEL_NOM>,
+ <RPM_SMD_REGULATOR_LEVEL_NOM_PLUS>,
+ <RPM_SMD_REGULATOR_LEVEL_TURBO>;
+
+ mem-acc-supply = <&gfx_mem_acc>;
+ qcom,mem-acc-corner-map = <1 1 2 2 2 2 2>;
+ qcom,ldo-init-voltage-adjustment = <10000 20000 30000>;
+ };
diff --git a/Documentation/devicetree/bindings/regulator/qpnp-regulator.txt b/Documentation/devicetree/bindings/regulator/qpnp-regulator.txt
new file mode 100644
index 0000000..601903b
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/qpnp-regulator.txt
@@ -0,0 +1,151 @@
+Qualcomm Technologies, Inc. QPNP Regulators
+
+qpnp-regulator is a regulator driver which supports regulators inside of PMICs
+that utilize the MSM SPMI implementation.
+
+Required properties:
+- compatible: Must be "qcom,qpnp-regulator"
+- reg: Specifies the SPMI address and size for this regulator device
+ Note, this is the only property which can be used within a
+ subnode.
+- regulator-name: A string used as a descriptive name for regulator outputs
+- parent-supply: phandle to the parent supply/regulator node
+
+Required structure:
+- A qcom,qpnp-regulator node must be a child of an spmi_device node.
+
+Optional properties:
+- interrupts: List of interrupts used by the regulator.
+- interrupt-names: List of strings defining the names of the
+ interrupts in the 'interrupts' property 1-to-1.
+ Supported values are "ocp" for voltage switch
+ type regulators. If an OCP interrupt is
+ specified, then the voltage switch will be
+ toggled off and back on when OCP triggers in
+ order to handle high in-rush current.
+- qcom,system-load: Load in uA present on regulator that is not
+ captured by any consumer request
+- qcom,enable-time: Time in us to delay after enabling the regulator
+- qcom,auto-mode-enable: 1 = Enable automatic hardware selection of
+ regulator mode (HPM vs LPM); not available on
+ boost type regulators
+ 0 = Disable auto mode selection
+- qcom,bypass-mode-enable: 1 = Enable bypass mode for an LDO type regulator
+ so that it acts like a switch and simply outputs
+ its input voltage
+ 0 = Do not enable bypass mode
+- qcom,ocp-enable: 1 = Allow over current protection (OCP) to be
+ enabled for voltage switch type regulators so
+ that they latch off automatically when over
+ current is detected. OCP is enabled when in
+ HPM or auto mode.
+ 0 = Disable OCP
+- qcom,ocp-max-retries: Maximum number of times to try toggling a voltage
+ switch off and back on as a result of
+ consecutive over current events.
+- qcom,ocp-retry-delay: Time to delay in milliseconds between each
+ voltage switch toggle after an over current
+ event takes place.
+- qcom,pull-down-enable: 1 = Enable output pull down resistor when the
+ regulator is disabled
+ 0 = Disable pull down resistor
+- qcom,soft-start-enable: 1 = Enable soft start for LDO and voltage switch
+ type regulators so that output voltage slowly
+ ramps up when the regulator is enabled
+ 0 = Disable soft start
+- qcom,boost-current-limit: This property sets the current limit of boost
+ type regulators; supported values are:
+ 0 = 300 mA
+ 1 = 600 mA
+ 2 = 900 mA
+ 3 = 1200 mA
+ 4 = 1500 mA
+ 5 = 1800 mA
+ 6 = 2100 mA
+ 7 = 2400 mA
+- qcom,pin-ctrl-enable: Bit mask specifying which hardware pins should be
+ used to enable the regulator, if any; supported
+ bits are:
+ 0 = ignore all hardware enable signals
+ BIT(0) = follow HW0_EN signal
+ BIT(1) = follow HW1_EN signal
+ BIT(2) = follow HW2_EN signal
+ BIT(3) = follow HW3_EN signal
+- qcom,pin-ctrl-hpm: Bit mask specifying which hardware pins should be
+ used to force the regulator into high power
+ mode, if any; supported bits are:
+ 0 = ignore all hardware enable signals
+ BIT(0) = follow HW0_EN signal
+ BIT(1) = follow HW1_EN signal
+ BIT(2) = follow HW2_EN signal
+ BIT(3) = follow HW3_EN signal
+ BIT(4) = follow PMIC awake state
+- qcom,vs-soft-start-strength: This property sets the soft start strength for
+ voltage switch type regulators; supported values
+ are:
+ 0 = 0.05 uA
+ 1 = 0.25 uA
+ 2 = 0.55 uA
+ 3 = 0.75 uA
+- qcom,hpm-enable: 1 = Enable high power mode (HPM), also referred
+ to as NPM. HPM consumes more ground current
+ than LPM, but it can source significantly higher
+ load current. HPM is not available on boost
+ type regulators. For voltage switch type
+ regulators, HPM implies that over current
+ protection and soft start are active all the
+ time. This configuration can be overwritten
+ by changing the regulator's mode dynamically.
+ 0 = Do not enable HPM
+- qcom,force-type: Override the type and subtype register values. Useful for some
+ regulators that have invalid types advertised by the hardware.
+ The format is two unsigned integers of the form <type subtype>.
+
+Note, if a given optional qcom,* binding is not present, then the qpnp-regulator
+driver will leave that feature in the default hardware state.
+
+All properties specified within the core regulator framework can also be used.
+These bindings can be found in regulator.txt.
+
+Example:
+ qcom,spmi@fc4c0000 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ interrupt-controller;
+ #interrupt-cells = <3>;
+
+ qcom,pm8941@1 {
+ reg = <0x1>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ regulator@1400 {
+ regulator-name = "8941_s1";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ compatible = "qcom,qpnp-regulator";
+ reg = <0x1400 0x300>;
+ regulator-min-microvolt = <1300000>;
+ regulator-max-microvolt = <1400000>;
+
+ qcom,ctl@1400 {
+ reg = <0x1400 0x100>;
+ };
+ qcom,ps@1500 {
+ reg = <0x1500 0x100>;
+ };
+ qcom,freq@1600 {
+ reg = <0x1600 0x100>;
+ };
+ };
+
+ regulator@4000 {
+ regulator-name = "8941_l1";
+ reg = <0x4000 0x100>;
+ compatible = "qcom,qpnp-regulator";
+ regulator-min-microvolt = <1225000>;
+ regulator-max-microvolt = <1300000>;
+ qcom,pull-down-enable = <1>;
+ };
+ };
+ };
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 63454b5..b5d3f08 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -1,3 +1,4 @@
source "drivers/power/avs/Kconfig"
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 ff35c71..b0cafa9 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_POWER_RESET) += reset/
obj-$(CONFIG_POWER_SUPPLY) += supply/
+obj-$(CONFIG_ARCH_QCOM) += qcom/
diff --git a/drivers/power/qcom/Kconfig b/drivers/power/qcom/Kconfig
new file mode 100644
index 0000000..8625aa8
--- /dev/null
+++ b/drivers/power/qcom/Kconfig
@@ -0,0 +1,11 @@
+menu "Miscellaneous Qualcomm Technologies, Inc. power support"
+
+config MSM_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.
+
+endmenu
diff --git a/drivers/power/qcom/Makefile b/drivers/power/qcom/Makefile
new file mode 100644
index 0000000..818179c
--- /dev/null
+++ b/drivers/power/qcom/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_MSM_APM) += apm.o
diff --git a/drivers/power/qcom/apm.c b/drivers/power/qcom/apm.c
new file mode 100644
index 0000000..3dfb439
--- /dev/null
+++ b/drivers/power/qcom/apm.c
@@ -0,0 +1,1062 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/arm-smccc.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/of_device.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/power/qcom/apm.h>
+#include <soc/qcom/scm.h>
+
+/*
+ * VDD_APCC
+ * =============================================================
+ * | VDD_MX | |
+ * | ==========================|============= |
+ * ___|___ ___|___ ___|___ ___|___ ___|___ ___|___
+ * | | | | | | | | | | | |
+ * | APCC | | MX HS | | MX HS | | APCC | | MX HS | | APCC |
+ * | HS | | | | | | HS | | | | HS |
+ * |_______| |_______| |_______| |_______| |_______| |_______|
+ * |_________| |_________| |__________|
+ * | | |
+ * ______|_____ ______|_____ _______|_____
+ * | | | | | |
+ * | | | | | |
+ * | CPU MEM | | L2 MEM | | L3 MEM |
+ * | Arrays | | Arrays | | Arrays |
+ * | | | | | |
+ * |____________| |____________| |_____________|
+ *
+ */
+
+/* Register value definitions */
+#define APCS_GFMUXA_SEL_VAL 0x13
+#define APCS_GFMUXA_DESEL_VAL 0x03
+#define MSM_APM_MX_MODE_VAL 0x00
+#define MSM_APM_APCC_MODE_VAL 0x10
+#define MSM_APM_MX_DONE_VAL 0x00
+#define MSM_APM_APCC_DONE_VAL 0x03
+#define MSM_APM_OVERRIDE_SEL_VAL 0xb0
+#define MSM_APM_SEC_CLK_SEL_VAL 0x30
+#define SPM_EVENT_SET_VAL 0x01
+#define SPM_EVENT_CLEAR_VAL 0x00
+
+/* Register bit mask definitions */
+#define MSM_APM_CTL_STS_MASK 0x0f
+
+/* Register offset definitions */
+#define APCC_APM_MODE 0x00000098
+#define APCC_APM_CTL_STS 0x000000a8
+#define APCS_SPARE 0x00000068
+#define APCS_VERSION 0x00000fd0
+
+#define HMSS_VERSION_1P2 0x10020000
+
+#define MSM_APM_SWITCH_TIMEOUT_US 10
+#define SPM_WAKEUP_DELAY_US 2
+#define SPM_EVENT_NUM 6
+
+#define MSM_APM_DRIVER_NAME "qcom,msm-apm"
+
+enum {
+ CLOCK_ASSERT_ENABLE,
+ CLOCK_ASSERT_DISABLE,
+ CLOCK_ASSERT_TOGGLE,
+};
+
+enum {
+ MSM8996_ID,
+ MSM8996PRO_ID,
+ MSM8953_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;
+ bool clk_src_override;
+ 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);
+
+static unsigned long __invoke_psci_fn_smc(unsigned long a, unsigned long b,
+ unsigned long c, unsigned long d)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(a, b, c, d, 0, 0, 0, 0, &res);
+
+ return res.a0;
+}
+
+/*
+ * 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->clk_src_override = of_property_read_bool(dev->of_node,
+ "qcom,clock-source-override");
+
+ if (ctrl->clk_src_override)
+ dev_info(dev, "overriding clock sources across APM switch\n");
+
+ 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;
+}
+
+/* MSM8953 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 MSM8953 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 msm_apm_secure_clock_source_override(
+ struct msm_apm_ctrl_dev *ctrl_dev, bool enable)
+{
+ int ret;
+
+ if (ctrl_dev->clk_src_override) {
+ ret = __invoke_psci_fn_smc(0xC4000020, 3, enable ?
+ CLOCK_ASSERT_ENABLE :
+ CLOCK_ASSERT_DISABLE, 0);
+ if (ret)
+ dev_err(ctrl_dev->dev, "PSCI request to switch to %s clock source failed\n",
+ enable ? "GPLL0" : "original");
+ }
+
+ return 0;
+}
+
+static int msm8996_apm_wait_for_switch(struct msm_apm_ctrl_dev *ctrl_dev,
+ u32 done_val)
+{
+ int timeout = MSM_APM_SWITCH_TIMEOUT_US;
+ u32 regval;
+
+ while (timeout > 0) {
+ regval = readl_relaxed(ctrl_dev->reg_base + APCC_APM_CTL_STS);
+ if ((regval & MSM_APM_CTL_STS_MASK) == done_val)
+ break;
+
+ udelay(1);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ dev_err(ctrl_dev->dev, "%s switch timed out. APCC_APM_CTL_STS=0x%x\n",
+ done_val == MSM_APM_MX_DONE_VAL
+ ? "APCC to MX" : "MX to APCC",
+ regval);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int msm8996_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int i, ret;
+
+ mutex_lock(&scm_lmh_lock);
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, true);
+ if (ret)
+ goto done;
+
+ /* 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();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_MX_DONE_VAL);
+
+ /* 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]);
+ }
+
+ /*
+ * Ensure that HMSS v1.0/v1.1 register writes are completed before
+ * bailing out in the case of a switching time out.
+ */
+ if (ret)
+ goto done;
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, false);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+ mutex_unlock(&scm_lmh_lock);
+
+ return ret;
+}
+
+static int msm8996_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int i, ret;
+
+ mutex_lock(&scm_lmh_lock);
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, true);
+ if (ret)
+ goto done;
+
+ /* 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();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_APCC_DONE_VAL);
+
+ /* 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);
+ }
+
+ /*
+ * Ensure that HMSS v1.0/v1.1 register writes are completed before
+ * bailing out in the case of a switching time out.
+ */
+ if (ret)
+ goto done;
+
+ ret = msm_apm_secure_clock_source_override(ctrl_dev, false);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+ mutex_unlock(&scm_lmh_lock);
+
+ return ret;
+}
+
+static int msm8996pro_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ /* 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();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_MX_DONE_VAL);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_MX;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to MX\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+
+ return ret;
+}
+
+static int msm8996pro_apm_switch_to_apcc(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&ctrl_dev->lock, flags);
+
+ /* 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();
+
+ ret = msm8996_apm_wait_for_switch(ctrl_dev, MSM_APM_APCC_DONE_VAL);
+ if (ret)
+ goto done;
+
+ ctrl_dev->supply = MSM_APM_SUPPLY_APCC;
+ dev_dbg(ctrl_dev->dev, "APM supply switched to APCC\n");
+
+done:
+ spin_unlock_irqrestore(&ctrl_dev->lock, flags);
+
+ return ret;
+}
+
+/* MSM8953 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
+
+/* MSM8953 register offset definitions */
+#define MSM8953_APCC_APM_MODE 0x000002a8
+#define MSM8953_APCC_APM_CTL_STS 0x000002b0
+
+static int msm8953_apm_switch_to_mx(struct msm_apm_ctrl_dev *ctrl_dev)
+{
+ int timeout = MSM_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 & MSM_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 = MSM_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 & MSM_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 MSM8996PRO_ID:
+ ret = msm8996pro_apm_switch_to_mx(ctrl_dev);
+ break;
+ case MSM8953_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 MSM8996PRO_ID:
+ ret = msm8996pro_apm_switch_to_apcc(ctrl_dev);
+ break;
+ case MSM8953_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", 0444, 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 const struct of_device_id msm_apm_match_table[] = {
+ {
+ .compatible = "qcom,msm-apm",
+ .data = (void *)(uintptr_t)MSM8996_ID,
+ },
+ {
+ .compatible = "qcom,msm8996pro-apm",
+ .data = (void *)(uintptr_t)MSM8996PRO_ID,
+ },
+ {
+ .compatible = "qcom,msm8953-apm",
+ .data = (void *)(uintptr_t)MSM8953_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)
+ 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:
+ case MSM8996PRO_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:
+ 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/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index f4ce22d..96a816d 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -888,6 +888,116 @@
This driver provides support for the voltage regulators on the
WM8994 CODEC.
+config REGULATOR_CPR3
+ bool "CPR3 regulator core support"
+ help
+ This driver supports Core Power Reduction (CPR) version 3 controllers
+ which are used by some Qualcomm Technologies, Inc. (QTI) 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_HMSS
+ bool "CPR3 regulator for HMSS"
+ depends on OF
+ select REGULATOR_CPR3
+ help
+ This driver supports Qualcomm Technologies, Inc. HMSS application
+ processor specific features including memory array power mux (APM)
+ switching, two CPR3 threads which monitor the two HMSS clusters that
+ are both powered by a shared supply, and hardware closed-loop auto
+ voltage stepping. This driver reads both initial voltage and CPR
+ target quotient values out of hardware fuses.
+
+config REGULATOR_CPR3_MMSS
+ bool "RBCPR3 regulator for MMSS"
+ depends on OF
+ select REGULATOR_CPR3
+ help
+ This driver supports Qualcomm Technologies, Inc. MMSS graphics
+ processor specific features. The MMSS CPR3 controller only uses one
+ thread to monitor the MMSS voltage requirements. This driver reads
+ initial voltage values out of hardware fuses and CPR target quotient
+ values out of device tree.
+
+config REGULATOR_CPR4_APSS
+ bool "CPR4 regulator for APSS"
+ depends on OF
+ select 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.
+
+config REGULATOR_CPR4_MMSS_LDO
+ bool "RBCPR3 regulator for MMSS LDO"
+ depends on OF
+ select REGULATOR_CPR3
+ help
+ This driver supports Qualcomm Technologies, Inc. MMSS graphics
+ processor specific features. The MMSS CPR3 controller only uses one
+ thread to monitor the MMSS LDO voltage requirements. This driver reads
+ initial voltage values out of hardware fuses and CPR target quotient
+ values out of device tree.
+
+config REGULATOR_CPRH_KBSS
+ bool "CPRH regulator for KBSS"
+ depends on OF
+ select REGULATOR_CPR3
+ help
+ This driver supports Qualcomm Technologies, Inc. KBSS application
+ processor specific features including CPR hardening (CPRh) and two
+ CPRh controllers which monitor the two KBSS clusters each powered by
+ independent voltage supplies. This driver reads both initial voltage
+ and CPR target quotient values out of hardware fuses.
+
+config REGULATOR_KRYO
+ bool "Kryo regulator driver"
+ depends on OF
+ help
+ Some MSM designs have CPUs that can be directly powered from a common
+ voltage rail via a Block Head Switch (BHS) or an LDO whose output
+ voltage can be configured for use when certain power constraints are
+ met. Say yes to support management of LDO and BHS modes for the
+ clusters in the CPU subsystem.
+
+config REGULATOR_MEM_ACC
+ tristate "QTI Memory accelerator regulator driver"
+ help
+ Say y here to enable the memory accelerator driver for Qualcomm
+ Technologies, Inc. (QTI) chips. The accelerator controls delays
+ applied for memory accesses. This driver configures the power-mode
+ (corner) for the memory accelerator.
+
+config REGULATOR_MSM_GFX_LDO
+ tristate "MSM GFX LDO Regulator"
+ depends on OF
+ help
+ This driver supports the MSM GFX (Graphics) LDO regulator. The
+ GFU core is either powered by an internal MSM LDO or by BHS.
+ Typically the lower voltage corners are powered by LDO and
+ the higher ones by BHS. This driver allows for configuration of
+ the rail between the LDO/BHS as well as the LDO voltage.
+
+config REGULATOR_QPNP
+ tristate "Qualcomm Technologies, Inc. QPNP regulator support"
+ depends on SPMI
+ help
+ This driver supports voltage regulators in Qualcomm Technologies, Inc.
+ PMIC chips which comply with QPNP. QPNP is a SPMI based PMIC
+ implementation. These chips provide several different varieties of
+ LDO and switching regulators. They also provide voltage switches and
+ boost regulators.
+
config REGULATOR_RPMH
tristate "Qualcomm Technologies, Inc. RPMh regulator driver"
depends on OF
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 2e466b1..1a96c07 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -114,6 +114,16 @@
obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
obj-$(CONFIG_REGULATOR_WM8994) += wm8994-regulator.o
+obj-$(CONFIG_REGULATOR_CPR3) += cpr3-regulator.o cpr3-util.o
+obj-$(CONFIG_REGULATOR_CPR3_HMSS) += cpr3-hmss-regulator.o
+obj-$(CONFIG_REGULATOR_CPR3_MMSS) += cpr3-mmss-regulator.o
+obj-$(CONFIG_REGULATOR_CPR4_APSS) += cpr4-apss-regulator.o
+obj-$(CONFIG_REGULATOR_CPR4_MMSS_LDO) += cpr4-mmss-ldo-regulator.o
+obj-$(CONFIG_REGULATOR_CPRH_KBSS) += cprh-kbss-regulator.o
+obj-$(CONFIG_REGULATOR_KRYO) += kryo-regulator.o
+obj-$(CONFIG_REGULATOR_MEM_ACC) += mem-acc-regulator.o
+obj-$(CONFIG_REGULATOR_MSM_GFX_LDO) += msm_gfx_ldo.o
+obj-$(CONFIG_REGULATOR_QPNP) += qpnp-regulator.o
obj-$(CONFIG_REGULATOR_RPMH) += rpmh-regulator.o
obj-$(CONFIG_REGULATOR_STUB) += stub-regulator.o
diff --git a/drivers/regulator/cpr3-hmss-regulator.c b/drivers/regulator/cpr3-hmss-regulator.c
new file mode 100644
index 0000000..77e8bf4b
--- /dev/null
+++ b/drivers/regulator/cpr3-hmss-regulator.c
@@ -0,0 +1,1829 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/msm-ldo-regulator.h>
+
+#include "cpr3-regulator.h"
+
+#define MSM8996_HMSS_FUSE_CORNERS 5
+
+/**
+ * struct cpr3_msm8996_hmss_fuses - HMSS specific fuse data for MSM8996
+ * @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
+ * @cbf_voltage_offset: Voltage margin offset for the CBF regulator used on
+ * MSM8996-Pro chips.
+ * @cpr_fusing_rev: CPR fusing revision fuse parameter value
+ * @redundant_fusing: Redundant fusing select fuse parameter value
+ * @limitation: CPR limitation select fuse parameter value
+ * @partial_binning: Chip partial binning fuse parameter value which defines
+ * limitations found on a given chip
+ * @vdd_mx_ret_fuse: Defines the logic retention voltage of VDD_MX
+ * @vdd_apcc_ret_fuse: Defines the logic retention voltage of VDD_APCC
+ * @aging_init_quot_diff: Initial quotient difference between CPR aging
+ * min and max sensors measured at time of manufacturing
+ *
+ * This struct holds the values for all of the fuses read from memory. The
+ * values for ro_sel, init_voltage, target_quot, and quot_offset come from
+ * either the primary or redundant fuse locations depending upon the value of
+ * redundant_fusing.
+ */
+struct cpr3_msm8996_hmss_fuses {
+ u64 ro_sel[MSM8996_HMSS_FUSE_CORNERS];
+ u64 init_voltage[MSM8996_HMSS_FUSE_CORNERS];
+ u64 target_quot[MSM8996_HMSS_FUSE_CORNERS];
+ u64 quot_offset[MSM8996_HMSS_FUSE_CORNERS];
+ u64 cbf_voltage_offset[MSM8996_HMSS_FUSE_CORNERS];
+ u64 speed_bin;
+ u64 cpr_fusing_rev;
+ u64 redundant_fusing;
+ u64 limitation;
+ u64 partial_binning;
+ u64 vdd_mx_ret_fuse;
+ u64 vdd_apcc_ret_fuse;
+ u64 aging_init_quot_diff;
+};
+
+/*
+ * Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 with speed bin fuse = 0.
+ * Fuse combos 8 - 15 map to CPR fusing revision 0 - 7 with speed bin fuse = 1.
+ */
+#define CPR3_MSM8996_HMSS_FUSE_COMBO_COUNT 16
+
+/*
+ * Constants which define the name of each fuse corner. Note that no actual
+ * fuses are defined for LowSVS. However, a mapping from corner to LowSVS
+ * is required in order to perform target quotient interpolation properly.
+ */
+enum cpr3_msm8996_hmss_fuse_corner {
+ CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS = 0,
+ CPR3_MSM8996_HMSS_FUSE_CORNER_LOWSVS = 1,
+ CPR3_MSM8996_HMSS_FUSE_CORNER_SVS = 2,
+ CPR3_MSM8996_HMSS_FUSE_CORNER_NOM = 3,
+ CPR3_MSM8996_HMSS_FUSE_CORNER_TURBO = 4,
+};
+
+static const char * const cpr3_msm8996_hmss_fuse_corner_name[] = {
+ [CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS] = "MinSVS",
+ [CPR3_MSM8996_HMSS_FUSE_CORNER_LOWSVS] = "LowSVS",
+ [CPR3_MSM8996_HMSS_FUSE_CORNER_SVS] = "SVS",
+ [CPR3_MSM8996_HMSS_FUSE_CORNER_NOM] = "NOM",
+ [CPR3_MSM8996_HMSS_FUSE_CORNER_TURBO] = "TURBO",
+};
+
+/* CPR3 hardware thread IDs */
+#define MSM8996_HMSS_POWER_CLUSTER_THREAD_ID 0
+#define MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID 1
+
+/*
+ * MSM8996 HMSS fuse parameter locations:
+ *
+ * Structs are organized with the following dimensions:
+ * Outer: 0 or 1 for power or performance cluster
+ * Middle: 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.
+ *
+ * Note that there are only physically 4 sets of fuse parameters which
+ * correspond to the MinSVS, SVS, NOM, and TURBO fuse corners. However, the SVS
+ * quotient offset fuse is used to define the target quotient for the LowSVS
+ * fuse corner. In order to utilize LowSVS, it must be treated as if it were a
+ * real fully defined fuse corner. Thus, LowSVS fuse parameter locations are
+ * specified. These locations duplicate the SVS values in order to simplify
+ * interpolation logic.
+ */
+static const struct cpr3_fuse_param
+msm8996_hmss_ro_sel_param[2][MSM8996_HMSS_FUSE_CORNERS][2] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{66, 38, 41}, {} },
+ {{66, 38, 41}, {} },
+ {{66, 38, 41}, {} },
+ {{66, 34, 37}, {} },
+ {{66, 30, 33}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{64, 54, 57}, {} },
+ {{64, 54, 57}, {} },
+ {{64, 54, 57}, {} },
+ {{64, 50, 53}, {} },
+ {{64, 46, 49}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_init_voltage_param[2][MSM8996_HMSS_FUSE_CORNERS][3] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{67, 0, 5}, {} },
+ {{66, 58, 63}, {} },
+ {{66, 58, 63}, {} },
+ {{66, 52, 57}, {} },
+ {{66, 46, 51}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{65, 16, 21}, {} },
+ {{65, 10, 15}, {} },
+ {{65, 10, 15}, {} },
+ {{65, 4, 9}, {} },
+ {{64, 62, 63}, {65, 0, 3}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_target_quot_param[2][MSM8996_HMSS_FUSE_CORNERS][3] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{67, 42, 53}, {} },
+ {{67, 30, 41}, {} },
+ {{67, 30, 41}, {} },
+ {{67, 18, 29}, {} },
+ {{67, 6, 17}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{65, 58, 63}, {66, 0, 5}, {} },
+ {{65, 46, 57}, {} },
+ {{65, 46, 57}, {} },
+ {{65, 34, 45}, {} },
+ {{65, 22, 33}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_quot_offset_param[2][MSM8996_HMSS_FUSE_CORNERS][3] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{} },
+ {{} },
+ {{68, 6, 13}, {} },
+ {{67, 62, 63}, {68, 0, 5}, {} },
+ {{67, 54, 61}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{} },
+ {{} },
+ {{66, 22, 29}, {} },
+ {{66, 14, 21}, {} },
+ {{66, 6, 13}, {} },
+ },
+};
+
+/*
+ * This fuse is used to define if the redundant set of fuses should be used for
+ * any particular feature. CPR is one such feature. The redundant CPR fuses
+ * should be used if this fuse parameter has a value of 1.
+ */
+static const struct cpr3_fuse_param msm8996_redundant_fusing_param[] = {
+ {73, 61, 63},
+ {},
+};
+#define MSM8996_CPR_REDUNDANT_FUSING 1
+
+static const struct cpr3_fuse_param
+msm8996_hmss_redun_ro_sel_param[2][MSM8996_HMSS_FUSE_CORNERS][2] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{76, 36, 39}, {} },
+ {{76, 32, 35}, {} },
+ {{76, 32, 35}, {} },
+ {{76, 28, 31}, {} },
+ {{76, 24, 27}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{74, 52, 55}, {} },
+ {{74, 48, 51}, {} },
+ {{74, 48, 51}, {} },
+ {{74, 44, 47}, {} },
+ {{74, 40, 43}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_redun_init_voltage_param[2][MSM8996_HMSS_FUSE_CORNERS][3] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{76, 58, 63}, {} },
+ {{76, 52, 57}, {} },
+ {{76, 52, 57}, {} },
+ {{76, 46, 51}, {} },
+ {{76, 40, 45}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{75, 10, 15}, {} },
+ {{75, 4, 9}, {} },
+ {{75, 4, 9}, {} },
+ {{74, 62, 63}, {75, 0, 3}, {} },
+ {{74, 56, 61}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_redun_target_quot_param[2][MSM8996_HMSS_FUSE_CORNERS][2] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{77, 36, 47}, {} },
+ {{77, 24, 35}, {} },
+ {{77, 24, 35}, {} },
+ {{77, 12, 23}, {} },
+ {{77, 0, 11}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{75, 52, 63}, {} },
+ {{75, 40, 51}, {} },
+ {{75, 40, 51}, {} },
+ {{75, 28, 39}, {} },
+ {{75, 16, 27}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_redun_quot_offset_param[2][MSM8996_HMSS_FUSE_CORNERS][2] = {
+ [MSM8996_HMSS_POWER_CLUSTER_THREAD_ID] = {
+ {{} },
+ {{} },
+ {{68, 11, 18}, {} },
+ {{77, 56, 63}, {} },
+ {{77, 48, 55}, {} },
+ },
+ [MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID] = {
+ {{} },
+ {{} },
+ {{76, 16, 23}, {} },
+ {{76, 8, 15}, {} },
+ {{76, 0, 7}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param msm8996_cpr_fusing_rev_param[] = {
+ {39, 51, 53},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8996_hmss_speed_bin_param[] = {
+ {38, 29, 31},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8996_cpr_limitation_param[] = {
+ {41, 31, 32},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8996_vdd_mx_ret_param[] = {
+ {41, 2, 4},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8996_vdd_apcc_ret_param[] = {
+ {41, 52, 54},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8996_cpr_partial_binning_param[] = {
+ {39, 55, 59},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8996_hmss_aging_init_quot_diff_param[] = {
+ {68, 14, 19},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8996pro_hmss_voltage_offset_param[MSM8996_HMSS_FUSE_CORNERS][4] = {
+ {{68, 50, 52}, {41, 63, 63}, {} },
+ {{62, 30, 31}, {62, 63, 63}, {66, 45, 45}, {} },
+ {{61, 35, 36}, {61, 62, 63}, {} },
+ {{61, 26, 26}, {61, 32, 34}, {} },
+ {{61, 22, 25}, {} },
+};
+
+#define MSM8996PRO_SOC_ID 4
+
+/*
+ * Some initial msm8996 parts cannot be used in a meaningful way by software.
+ * Other parts can only be used when operating with CPR disabled (i.e. at the
+ * fused open-loop voltage) when no voltage interpolation is applied. A fuse
+ * parameter is provided so that software can properly handle these limitations.
+ */
+enum msm8996_cpr_limitation {
+ MSM8996_CPR_LIMITATION_NONE = 0,
+ MSM8996_CPR_LIMITATION_UNSUPPORTED = 2,
+ MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION = 3,
+};
+
+/*
+ * Some initial msm8996 parts cannot be operated at low voltages. A fuse
+ * parameter is provided so that software can properly handle these limitations.
+ */
+enum msm8996_cpr_partial_binning {
+ MSM8996_CPR_PARTIAL_BINNING_SVS = 11,
+ MSM8996_CPR_PARTIAL_BINNING_NOM = 12,
+};
+
+/* Additional MSM8996 specific data: */
+
+/* Open loop voltage fuse reference voltages in microvolts for MSM8996 v1/v2 */
+static const int msm8996_v1_v2_hmss_fuse_ref_volt[MSM8996_HMSS_FUSE_CORNERS] = {
+ 605000,
+ 745000, /* Place holder entry for LowSVS */
+ 745000,
+ 905000,
+ 1015000,
+};
+
+/* Open loop voltage fuse reference voltages in microvolts for MSM8996 v3 */
+static const int msm8996_v3_hmss_fuse_ref_volt[MSM8996_HMSS_FUSE_CORNERS] = {
+ 605000,
+ 745000, /* Place holder entry for LowSVS */
+ 745000,
+ 905000,
+ 1140000,
+};
+
+/*
+ * Open loop voltage fuse reference voltages in microvolts for MSM8996 v3 with
+ * speed_bin == 1 and cpr_fusing_rev >= 5.
+ */
+static const int msm8996_v3_speed_bin1_rev5_hmss_fuse_ref_volt[
+ MSM8996_HMSS_FUSE_CORNERS] = {
+ 605000,
+ 745000, /* Place holder entry for LowSVS */
+ 745000,
+ 905000,
+ 1040000,
+};
+
+/* Defines mapping from retention fuse values to voltages in microvolts */
+static const int msm8996_vdd_apcc_fuse_ret_volt[] = {
+ 600000, 550000, 500000, 450000, 400000, 350000, 300000, 600000,
+};
+
+static const int msm8996_vdd_mx_fuse_ret_volt[] = {
+ 700000, 650000, 580000, 550000, 490000, 490000, 490000, 490000,
+};
+
+#define MSM8996_HMSS_FUSE_STEP_VOLT 10000
+#define MSM8996_HMSS_VOLTAGE_FUSE_SIZE 6
+#define MSM8996PRO_HMSS_CBF_FUSE_STEP_VOLT 10000
+#define MSM8996PRO_HMSS_CBF_VOLTAGE_FUSE_SIZE 4
+#define MSM8996_HMSS_QUOT_OFFSET_SCALE 5
+#define MSM8996_HMSS_AGING_INIT_QUOT_DIFF_SCALE 2
+#define MSM8996_HMSS_AGING_INIT_QUOT_DIFF_SIZE 6
+
+#define MSM8996_HMSS_CPR_SENSOR_COUNT 25
+#define MSM8996_HMSS_THREAD0_SENSOR_MIN 0
+#define MSM8996_HMSS_THREAD0_SENSOR_MAX 14
+#define MSM8996_HMSS_THREAD1_SENSOR_MIN 15
+#define MSM8996_HMSS_THREAD1_SENSOR_MAX 24
+
+#define MSM8996_HMSS_CPR_CLOCK_RATE 19200000
+
+#define MSM8996_HMSS_AGING_SENSOR_ID 11
+#define MSM8996_HMSS_AGING_BYPASS_MASK0 (GENMASK(7, 0) & ~BIT(3))
+
+/**
+ * cpr3_msm8996_hmss_use_voltage_offset_fuse() - return if this part utilizes
+ * voltage offset fuses or not
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: true if this part utilizes voltage offset fuses, else false
+ */
+static inline bool cpr3_msm8996_hmss_use_voltage_offset_fuse(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_hmss_fuses *fuse = vreg->platform_fuses;
+
+ return vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID
+ && fuse->cpr_fusing_rev >= 1
+ && of_property_read_bool(vreg->of_node, "qcom,is-cbf-regulator");
+}
+
+/**
+ * cpr3_msm8996_hmss_read_fuse_data() - load HMSS specific fuse parameter values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function allocates a cpr3_msm8996_hmss_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_msm8996_hmss_read_fuse_data(struct cpr3_regulator *vreg)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ struct cpr3_msm8996_hmss_fuses *fuse;
+ bool redundant;
+ int i, id, rc;
+
+ fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
+ if (!fuse)
+ return -ENOMEM;
+
+ rc = cpr3_read_fuse_param(base, msm8996_hmss_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, msm8996_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, msm8996_redundant_fusing_param,
+ &fuse->redundant_fusing);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read redundant fusing config fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ redundant = (fuse->redundant_fusing == MSM8996_CPR_REDUNDANT_FUSING);
+ cpr3_info(vreg, "using redundant fuses = %c\n",
+ redundant ? 'Y' : 'N');
+
+ rc = cpr3_read_fuse_param(base, msm8996_cpr_limitation_param,
+ &fuse->limitation);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read CPR limitation fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+ cpr3_info(vreg, "CPR limitation = %s\n",
+ fuse->limitation == MSM8996_CPR_LIMITATION_UNSUPPORTED
+ ? "unsupported chip" : fuse->limitation
+ == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION
+ ? "CPR disabled and no interpolation" : "none");
+
+ rc = cpr3_read_fuse_param(base, msm8996_cpr_partial_binning_param,
+ &fuse->partial_binning);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read partial binning fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+ cpr3_info(vreg, "CPR partial binning limitation = %s\n",
+ fuse->partial_binning == MSM8996_CPR_PARTIAL_BINNING_SVS
+ ? "SVS min voltage"
+ : fuse->partial_binning == MSM8996_CPR_PARTIAL_BINNING_NOM
+ ? "NOM min voltage"
+ : "none");
+
+ rc = cpr3_read_fuse_param(base, msm8996_vdd_mx_ret_param,
+ &fuse->vdd_mx_ret_fuse);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read VDD_MX retention fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_read_fuse_param(base, msm8996_vdd_apcc_ret_param,
+ &fuse->vdd_apcc_ret_fuse);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read VDD_APCC retention fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cpr3_info(vreg, "Retention voltage fuses: VDD_MX = %llu, VDD_APCC = %llu\n",
+ fuse->vdd_mx_ret_fuse, fuse->vdd_apcc_ret_fuse);
+
+ rc = cpr3_read_fuse_param(base, msm8996_hmss_aging_init_quot_diff_param,
+ &fuse->aging_init_quot_diff);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read aging initial quotient difference fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ id = vreg->thread->thread_id;
+
+ for (i = 0; i < MSM8996_HMSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ redundant
+ ? msm8996_hmss_redun_init_voltage_param[id][i]
+ : msm8996_hmss_init_voltage_param[id][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,
+ redundant
+ ? msm8996_hmss_redun_target_quot_param[id][i]
+ : msm8996_hmss_target_quot_param[id][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,
+ redundant
+ ? msm8996_hmss_redun_ro_sel_param[id][i]
+ : msm8996_hmss_ro_sel_param[id][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,
+ redundant
+ ? msm8996_hmss_redun_quot_offset_param[id][i]
+ : msm8996_hmss_quot_offset_param[id][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;
+ }
+ }
+
+ vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
+ if (vreg->fuse_combo >= CPR3_MSM8996_HMSS_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 = MSM8996_HMSS_FUSE_CORNERS;
+ vreg->platform_fuses = fuse;
+
+ if (cpr3_msm8996_hmss_use_voltage_offset_fuse(vreg)) {
+ for (i = 0; i < MSM8996_HMSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ msm8996pro_hmss_voltage_offset_param[i],
+ &fuse->cbf_voltage_offset[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d CBF voltage offset fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_apply_fused_voltage_offset() - adjust the fused voltages for each
+ * fuse corner according to voltage offset fuse values
+ * @vreg: Pointer to the CPR3 regulator
+ * @fuse_volt: Pointer to an array of the fused voltage values; must
+ * have length equal to vreg->fuse_corner_count
+ *
+ * Voltage values in fuse_volt are modified in place.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_hmss_apply_fused_voltage_offset(struct cpr3_regulator *vreg,
+ int *fuse_volt)
+{
+ struct cpr3_msm8996_hmss_fuses *fuse = vreg->platform_fuses;
+ int i;
+
+ if (!cpr3_msm8996_hmss_use_voltage_offset_fuse(vreg))
+ return 0;
+
+ for (i = 0; i < vreg->fuse_corner_count; i++)
+ fuse_volt[i] += cpr3_convert_open_loop_voltage_fuse(
+ 0,
+ MSM8996PRO_HMSS_CBF_FUSE_STEP_VOLT,
+ fuse->cbf_voltage_offset[i],
+ MSM8996PRO_HMSS_CBF_VOLTAGE_FUSE_SIZE);
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_parse_corner_data() - parse HMSS 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_hmss_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_msm8996_hmss_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 both device tree and in
+ * hardware fuses, 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_msm8996_hmss_calculate_open_loop_voltages(
+ struct cpr3_regulator *vreg)
+{
+ struct device_node *node = vreg->of_node;
+ struct cpr3_msm8996_hmss_fuses *fuse = vreg->platform_fuses;
+ int rc = 0;
+ bool allow_interpolation;
+ u64 freq_low, volt_low, freq_high, volt_high;
+ int i, j, soc_revision;
+ const int *ref_volt;
+ 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;
+ }
+
+ soc_revision = vreg->thread->ctrl->soc_revision;
+ if (soc_revision == 1 || soc_revision == 2)
+ ref_volt = msm8996_v1_v2_hmss_fuse_ref_volt;
+ else if (soc_revision == 3 && fuse->speed_bin == 1
+ && fuse->cpr_fusing_rev >= 5)
+ ref_volt = msm8996_v3_speed_bin1_rev5_hmss_fuse_ref_volt;
+ else
+ ref_volt = msm8996_v3_hmss_fuse_ref_volt;
+
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
+ ref_volt[i],
+ MSM8996_HMSS_FUSE_STEP_VOLT, fuse->init_voltage[i],
+ MSM8996_HMSS_VOLTAGE_FUSE_SIZE);
+
+ /* Log fused open-loop voltage values for debugging purposes. */
+ if (i != CPR3_MSM8996_HMSS_FUSE_CORNER_LOWSVS)
+ cpr3_info(vreg, "fused %6s: open-loop=%7d uV\n",
+ cpr3_msm8996_hmss_fuse_corner_name[i],
+ fuse_volt[i]);
+ }
+
+ if (cpr3_msm8996_hmss_use_voltage_offset_fuse(vreg)) {
+ rc = cpr3_hmss_apply_fused_voltage_offset(vreg, fuse_volt);
+ if (rc) {
+ cpr3_err(vreg, "could not apply CBF voltage offsets, rc=%d\n",
+ rc);
+ goto done;
+ }
+
+ for (i = 0; i < vreg->fuse_corner_count; i++)
+ cpr3_info(vreg, "fused %6s: CBF offset open-loop=%7d uV\n",
+ cpr3_msm8996_hmss_fuse_corner_name[i],
+ fuse_volt[i]);
+ }
+
+ 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");
+
+ /*
+ * No LowSVS open-loop voltage fuse exists. Instead, intermediate
+ * voltages are interpolated between MinSVS and SVS. Set the LowSVS
+ * voltage to be equal to the adjusted SVS voltage in order to avoid
+ * triggering an incorrect condition violation in the following loop.
+ */
+ fuse_volt[CPR3_MSM8996_HMSS_FUSE_CORNER_LOWSVS]
+ = fuse_volt[CPR3_MSM8996_HMSS_FUSE_CORNER_SVS];
+
+ for (i = 1; i < vreg->fuse_corner_count; i++) {
+ if (fuse_volt[i] < fuse_volt[i - 1]) {
+ cpr3_debug(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 (fuse->limitation == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION)
+ allow_interpolation = false;
+
+ 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;
+ }
+
+ for (i = 0; i < vreg->fuse_corner_count; i++)
+ fmax_corner[i] = vreg->fuse_corner_map[i];
+
+ /*
+ * 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];
+
+ /*
+ * Interpolation is not possible for corners mapped above the highest
+ * fuse corner so use the fuse corner value directly.
+ */
+ j = vreg->fuse_corner_count - 1;
+ for (i = fmax_corner[j] + 1; i < vreg->corner_count; i++)
+ vreg->corner[i].open_loop_volt = fuse_volt[j];
+
+ /*
+ * Corner LowSVS should be skipped for voltage interpolation
+ * since no fuse exists for it. Instead, the lowest interpolation
+ * should be between MinSVS and SVS.
+ */
+ for (i = CPR3_MSM8996_HMSS_FUSE_CORNER_LOWSVS;
+ i < vreg->fuse_corner_count - 1; i++) {
+ fmax_corner[i] = fmax_corner[i + 1];
+ fuse_volt[i] = fuse_volt[i + 1];
+ }
+
+ /* Interpolate voltages for the higher fuse corners. */
+ for (i = 1; i < vreg->fuse_corner_count - 1; 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_msm8996_hmss_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 cpr3_msm8996_hmss_set_no_interpolation_quotients(
+ struct cpr3_regulator *vreg, int *volt_adjust,
+ int *volt_adjust_fuse, int *ro_scale)
+{
+ struct cpr3_msm8996_hmss_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;
+ 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;
+}
+
+/**
+ * cpr3_msm8996_hmss_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 both device tree and in
+ * hardware fuses, 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 cpr3_msm8996_hmss_calculate_target_quotients(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_hmss_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;
+
+ /* Log fused quotient values for debugging purposes. */
+ cpr3_info(vreg, "fused MinSVS: quot[%2llu]=%4llu\n",
+ fuse->ro_sel[CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS],
+ fuse->target_quot[CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS]);
+ for (i = CPR3_MSM8996_HMSS_FUSE_CORNER_SVS;
+ i <= CPR3_MSM8996_HMSS_FUSE_CORNER_TURBO; i++)
+ cpr3_info(vreg, "fused %6s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
+ cpr3_msm8996_hmss_fuse_corner_name[i],
+ fuse->ro_sel[i], fuse->target_quot[i], fuse->ro_sel[i],
+ fuse->quot_offset[i] * MSM8996_HMSS_QUOT_OFFSET_SCALE);
+
+ allow_interpolation = of_property_read_bool(vreg->of_node,
+ "qcom,allow-quotient-interpolation");
+
+ if (fuse->limitation == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION)
+ allow_interpolation = false;
+
+ 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;
+ }
+
+ rc = cpr3_hmss_apply_fused_voltage_offset(vreg, volt_adjust_fuse);
+ if (rc) {
+ cpr3_err(vreg, "could not apply CBF voltage offsets, rc=%d\n",
+ rc);
+ goto done;
+ }
+
+ if (!allow_interpolation) {
+ /* Use fused target quotients for lower frequencies. */
+ return cpr3_msm8996_hmss_set_no_interpolation_quotients(vreg,
+ volt_adjust, volt_adjust_fuse, ro_scale);
+ }
+
+ for (i = 0; i < vreg->fuse_corner_count; i++)
+ fmax_corner[i] = vreg->fuse_corner_map[i];
+
+ /*
+ * Interpolation is not possible for corners mapped to the lowest fuse
+ * corner so use the fuse corner value directly.
+ */
+ i = CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS;
+ quot_adjust = cpr3_quot_adjustment(ro_scale[i], volt_adjust_fuse[i]);
+ quot = fuse->target_quot[i] + quot_adjust;
+ quot_high[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[CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS]; i++)
+ vreg->corner[i].target_quot[ro] = quot;
+
+ /*
+ * Interpolation is not possible for corners mapped above the highest
+ * fuse corner so use the fuse corner value directly.
+ */
+ j = vreg->fuse_corner_count - 1;
+ quot_adjust = cpr3_quot_adjustment(ro_scale[j], volt_adjust_fuse[j]);
+ quot = fuse->target_quot[j] + quot_adjust;
+ ro = fuse->ro_sel[j];
+ for (i = fmax_corner[j] + 1; i < vreg->corner_count; i++)
+ vreg->corner[i].target_quot[ro] = quot;
+
+ /*
+ * The LowSVS target quotient is defined as:
+ * (SVS target quotient) - (the unpacked SVS quotient offset)
+ * MinSVS, LowSVS, and SVS fuse corners all share the same RO so it is
+ * possible to interpolate between their target quotient values.
+ */
+ i = CPR3_MSM8996_HMSS_FUSE_CORNER_LOWSVS;
+ quot_high[i] = fuse->target_quot[CPR3_MSM8996_HMSS_FUSE_CORNER_SVS]
+ - fuse->quot_offset[CPR3_MSM8996_HMSS_FUSE_CORNER_SVS]
+ * MSM8996_HMSS_QUOT_OFFSET_SCALE;
+ quot_low[i] = fuse->target_quot[CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS];
+ if (quot_high[i] < quot_low[i]) {
+ cpr3_info(vreg, "quot_lowsvs=%llu < quot_minsvs=%llu; overriding: quot_lowsvs=%llu\n",
+ quot_high[i], quot_low[i], quot_low[i]);
+ quot_high[i] = quot_low[i];
+ }
+ if (fuse->ro_sel[CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS]
+ != fuse->ro_sel[CPR3_MSM8996_HMSS_FUSE_CORNER_SVS]) {
+ cpr3_info(vreg, "MinSVS RO=%llu != SVS RO=%llu; disabling LowSVS interpolation\n",
+ fuse->ro_sel[CPR3_MSM8996_HMSS_FUSE_CORNER_MINSVS],
+ fuse->ro_sel[CPR3_MSM8996_HMSS_FUSE_CORNER_SVS]);
+ quot_low[i] = quot_high[i];
+ }
+
+ for (i = CPR3_MSM8996_HMSS_FUSE_CORNER_SVS;
+ 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]
+ * MSM8996_HMSS_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;
+}
+
+/**
+ * cpr3_msm8996_partial_binning_override() - override the voltage and quotient
+ * settings for low corners based upon the value of the partial
+ * binning fuse
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Some parts are not able to operate at low voltages. The partial binning
+ * fuse specifies if a given part has such limitations.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_msm8996_partial_binning_override(struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_hmss_fuses *fuse = vreg->platform_fuses;
+ int i, fuse_corner, fmax_corner;
+
+ if (fuse->partial_binning == MSM8996_CPR_PARTIAL_BINNING_SVS)
+ fuse_corner = CPR3_MSM8996_HMSS_FUSE_CORNER_SVS;
+ else if (fuse->partial_binning == MSM8996_CPR_PARTIAL_BINNING_NOM)
+ fuse_corner = CPR3_MSM8996_HMSS_FUSE_CORNER_NOM;
+ else
+ return 0;
+
+ cpr3_info(vreg, "overriding voltages and quotients for all corners below %s Fmax\n",
+ cpr3_msm8996_hmss_fuse_corner_name[fuse_corner]);
+
+ fmax_corner = -1;
+ for (i = vreg->corner_count - 1; i >= 0; i--) {
+ if (vreg->corner[i].cpr_fuse_corner == fuse_corner) {
+ fmax_corner = i;
+ break;
+ }
+ }
+ if (fmax_corner < 0) {
+ cpr3_err(vreg, "could not find %s Fmax corner\n",
+ cpr3_msm8996_hmss_fuse_corner_name[fuse_corner]);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < fmax_corner; i++)
+ vreg->corner[i] = vreg->corner[fmax_corner];
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_print_settings() - print out HMSS CPR configuration settings into
+ * the kernel log for debugging purposes
+ * @vreg: Pointer to the CPR3 regulator
+ */
+static void cpr3_hmss_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_hmss_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_hmss_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;
+}
+
+#define MAX_VREG_NAME_SIZE 25
+/**
+ * cpr3_hmss_kvreg_init() - initialize HMSS Kryo Regulator data for a CPR3
+ * regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function loads Kryo Regulator data from device tree if it is present
+ * and requests a handle to the appropriate Kryo regulator device. In addition,
+ * it initializes Kryo Regulator data originating from hardware fuses, such as
+ * the LDO retention voltage, and requests the Kryo retention regulator to
+ * be configured to that value.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_hmss_kvreg_init(struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_hmss_fuses *fuse = vreg->platform_fuses;
+ struct device_node *node = vreg->of_node;
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ int id = vreg->thread->thread_id;
+ char kvreg_name_buf[MAX_VREG_NAME_SIZE];
+ int rc;
+
+ scnprintf(kvreg_name_buf, MAX_VREG_NAME_SIZE,
+ "vdd-thread%d-ldo-supply", id);
+
+ if (!of_find_property(ctrl->dev->of_node, kvreg_name_buf, NULL))
+ return 0;
+ else if (!of_find_property(node, "qcom,ldo-min-headroom-voltage", NULL))
+ return 0;
+
+ scnprintf(kvreg_name_buf, MAX_VREG_NAME_SIZE, "vdd-thread%d-ldo", id);
+
+ vreg->ldo_regulator = devm_regulator_get(ctrl->dev, kvreg_name_buf);
+ if (IS_ERR(vreg->ldo_regulator)) {
+ rc = PTR_ERR(vreg->ldo_regulator);
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(vreg, "unable to request %s regulator, rc=%d\n",
+ kvreg_name_buf, rc);
+ return rc;
+ }
+
+ vreg->ldo_regulator_bypass = BHS_MODE;
+
+ scnprintf(kvreg_name_buf, MAX_VREG_NAME_SIZE, "vdd-thread%d-ldo-ret",
+ id);
+
+ vreg->ldo_ret_regulator = devm_regulator_get(ctrl->dev, kvreg_name_buf);
+ if (IS_ERR(vreg->ldo_ret_regulator)) {
+ rc = PTR_ERR(vreg->ldo_ret_regulator);
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(vreg, "unable to request %s regulator, rc=%d\n",
+ kvreg_name_buf, rc);
+ return rc;
+ }
+
+ if (!ctrl->system_supply_max_volt) {
+ cpr3_err(ctrl, "system-supply max voltage must be specified\n");
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(node, "qcom,ldo-min-headroom-voltage",
+ &vreg->ldo_min_headroom_volt);
+ if (rc) {
+ cpr3_err(vreg, "error reading qcom,ldo-min-headroom-voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,ldo-max-headroom-voltage",
+ &vreg->ldo_max_headroom_volt);
+ if (rc) {
+ cpr3_err(vreg, "error reading qcom,ldo-max-headroom-voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,ldo-max-voltage",
+ &vreg->ldo_max_volt);
+ if (rc) {
+ cpr3_err(vreg, "error reading qcom,ldo-max-voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* Determine the CPU retention voltage based on fused data */
+ vreg->ldo_ret_volt =
+ max(msm8996_vdd_apcc_fuse_ret_volt[fuse->vdd_apcc_ret_fuse],
+ msm8996_vdd_mx_fuse_ret_volt[fuse->vdd_mx_ret_fuse]);
+
+ rc = regulator_set_voltage(vreg->ldo_ret_regulator, vreg->ldo_ret_volt,
+ INT_MAX);
+ if (rc < 0) {
+ cpr3_err(vreg, "regulator_set_voltage(ldo_ret) == %d failed, rc=%d\n",
+ vreg->ldo_ret_volt, rc);
+ return rc;
+ }
+
+ /* optional properties, do not error out if missing */
+ of_property_read_u32(node, "qcom,ldo-adjust-voltage",
+ &vreg->ldo_adjust_volt);
+
+ vreg->ldo_mode_allowed = !of_property_read_bool(node,
+ "qcom,ldo-disable");
+
+ cpr3_info(vreg, "LDO min headroom=%d uV, LDO max headroom=%d uV, LDO adj=%d uV, LDO mode=%s, LDO retention=%d uV\n",
+ vreg->ldo_min_headroom_volt,
+ vreg->ldo_max_headroom_volt,
+ vreg->ldo_adjust_volt,
+ vreg->ldo_mode_allowed ? "allowed" : "disallowed",
+ vreg->ldo_ret_volt);
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_mem_acc_init() - initialize mem-acc regulator data for
+ * a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function loads mem-acc data from device tree to enable
+ * the control of mem-acc settings based upon the CPR3 regulator
+ * output voltage.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_hmss_mem_acc_init(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ int id = vreg->thread->thread_id;
+ char mem_acc_vreg_name_buf[MAX_VREG_NAME_SIZE];
+ int rc;
+
+ scnprintf(mem_acc_vreg_name_buf, MAX_VREG_NAME_SIZE,
+ "mem-acc-thread%d-supply", id);
+
+ if (!of_find_property(ctrl->dev->of_node, mem_acc_vreg_name_buf,
+ NULL)) {
+ cpr3_debug(vreg, "not using memory accelerator regulator\n");
+ return 0;
+ } else if (!of_property_read_bool(vreg->of_node, "qcom,uses-mem-acc")) {
+ return 0;
+ }
+
+ scnprintf(mem_acc_vreg_name_buf, MAX_VREG_NAME_SIZE,
+ "mem-acc-thread%d", id);
+
+ vreg->mem_acc_regulator = devm_regulator_get(ctrl->dev,
+ mem_acc_vreg_name_buf);
+ if (IS_ERR(vreg->mem_acc_regulator)) {
+ rc = PTR_ERR(vreg->mem_acc_regulator);
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(vreg, "unable to request %s regulator, rc=%d\n",
+ mem_acc_vreg_name_buf, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_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_hmss_init_regulator(struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_hmss_fuses *fuse;
+ int rc;
+
+ rc = cpr3_msm8996_hmss_read_fuse_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = cpr3_hmss_kvreg_init(vreg);
+ if (rc) {
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(vreg, "unable to initialize Kryo Regulator settings, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_hmss_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;
+ }
+
+ fuse = vreg->platform_fuses;
+ if (fuse->limitation == MSM8996_CPR_LIMITATION_UNSUPPORTED) {
+ cpr3_err(vreg, "this chip requires an unsupported voltage\n");
+ return -EPERM;
+ } else if (fuse->limitation
+ == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION) {
+ vreg->thread->ctrl->cpr_allowed_hw = false;
+ }
+
+ rc = of_property_read_u32(vreg->of_node, "qcom,cpr-pd-bypass-mask",
+ &vreg->pd_bypass_mask);
+ if (rc) {
+ cpr3_err(vreg, "error reading qcom,cpr-pd-bypass-mask, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_hmss_parse_corner_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR corner data from device tree, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (of_find_property(vreg->of_node, "qcom,cpr-dynamic-floor-corner",
+ NULL)) {
+ rc = cpr3_parse_array_property(vreg,
+ "qcom,cpr-dynamic-floor-corner",
+ 1, &vreg->dynamic_floor_corner);
+ if (rc) {
+ cpr3_err(vreg, "error reading qcom,cpr-dynamic-floor-corner, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (vreg->dynamic_floor_corner <= 0) {
+ vreg->uses_dynamic_floor = false;
+ } else if (vreg->dynamic_floor_corner < CPR3_CORNER_OFFSET
+ || vreg->dynamic_floor_corner
+ > vreg->corner_count - 1 + CPR3_CORNER_OFFSET) {
+ cpr3_err(vreg, "dynamic floor corner=%d not in range [%d, %d]\n",
+ vreg->dynamic_floor_corner, CPR3_CORNER_OFFSET,
+ vreg->corner_count - 1 + CPR3_CORNER_OFFSET);
+ return -EINVAL;
+ }
+
+ vreg->dynamic_floor_corner -= CPR3_CORNER_OFFSET;
+ vreg->uses_dynamic_floor = true;
+ }
+
+ rc = cpr3_msm8996_hmss_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 = cpr3_msm8996_hmss_calculate_target_quotients(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to calculate target quotients, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_msm8996_partial_binning_override(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to override voltages and quotients based on partial binning fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cpr3_hmss_print_settings(vreg);
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_init_aging() - perform HMSS CPR3 controller specific
+ * aging initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_hmss_init_aging(struct cpr3_controller *ctrl)
+{
+ struct cpr3_msm8996_hmss_fuses *fuse = NULL;
+ struct cpr3_regulator *vreg;
+ u32 aging_ro_scale;
+ int i, j, rc;
+
+ for (i = 0; i < ctrl->thread_count; i++) {
+ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
+ if (ctrl->thread[i].vreg[j].aging_allowed) {
+ ctrl->aging_required = true;
+ vreg = &ctrl->thread[i].vreg[j];
+ fuse = vreg->platform_fuses;
+ break;
+ }
+ }
+ }
+
+ if (!ctrl->aging_required || !fuse || !vreg)
+ return 0;
+
+ rc = cpr3_parse_array_property(vreg, "qcom,cpr-aging-ro-scaling-factor",
+ 1, &aging_ro_scale);
+ if (rc)
+ return rc;
+
+ if (aging_ro_scale == 0) {
+ cpr3_err(ctrl, "aging RO scaling factor is invalid: %u\n",
+ aging_ro_scale);
+ return -EINVAL;
+ }
+
+ ctrl->aging_vdd_mode = REGULATOR_MODE_NORMAL;
+ ctrl->aging_complete_vdd_mode = REGULATOR_MODE_IDLE;
+
+ ctrl->aging_sensor_count = 1;
+ ctrl->aging_sensor = kzalloc(sizeof(*ctrl->aging_sensor), GFP_KERNEL);
+ if (!ctrl->aging_sensor)
+ return -ENOMEM;
+
+ ctrl->aging_sensor->sensor_id = MSM8996_HMSS_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0] = MSM8996_HMSS_AGING_BYPASS_MASK0;
+ ctrl->aging_sensor->ro_scale = aging_ro_scale;
+
+ ctrl->aging_sensor->init_quot_diff
+ = cpr3_convert_open_loop_voltage_fuse(0,
+ MSM8996_HMSS_AGING_INIT_QUOT_DIFF_SCALE,
+ fuse->aging_init_quot_diff,
+ MSM8996_HMSS_AGING_INIT_QUOT_DIFF_SIZE);
+
+ cpr3_debug(ctrl, "sensor %u aging init quotient diff = %d, aging RO scale = %u QUOT/V\n",
+ ctrl->aging_sensor->sensor_id,
+ ctrl->aging_sensor->init_quot_diff,
+ ctrl->aging_sensor->ro_scale);
+
+ return 0;
+}
+
+/**
+ * cpr3_hmss_init_controller() - perform HMSS CPR3 controller specific
+ * initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_hmss_init_controller(struct cpr3_controller *ctrl)
+{
+ int i, 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;
+ }
+
+ 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-supply regulator, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-up-down-delay-time",
+ &ctrl->up_down_delay_time);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,cpr-up-down-delay-time, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* No error check since this is an optional property. */
+ of_property_read_u32(ctrl->dev->of_node,
+ "qcom,system-supply-max-voltage",
+ &ctrl->system_supply_max_volt);
+
+ /* No error check since this is an optional property. */
+ of_property_read_u32(ctrl->dev->of_node, "qcom,cpr-clock-throttling",
+ &ctrl->proc_clock_throttle);
+
+ 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;
+ }
+
+ ctrl->sensor_count = MSM8996_HMSS_CPR_SENSOR_COUNT;
+
+ ctrl->sensor_owner = devm_kcalloc(ctrl->dev, ctrl->sensor_count,
+ sizeof(*ctrl->sensor_owner), GFP_KERNEL);
+ if (!ctrl->sensor_owner)
+ return -ENOMEM;
+
+ /* Specify sensor ownership */
+ for (i = MSM8996_HMSS_THREAD0_SENSOR_MIN;
+ i <= MSM8996_HMSS_THREAD0_SENSOR_MAX; i++)
+ ctrl->sensor_owner[i] = 0;
+ for (i = MSM8996_HMSS_THREAD1_SENSOR_MIN;
+ i <= MSM8996_HMSS_THREAD1_SENSOR_MAX; i++)
+ ctrl->sensor_owner[i] = 1;
+
+ ctrl->cpr_clock_rate = MSM8996_HMSS_CPR_CLOCK_RATE;
+ ctrl->ctrl_type = CPR_CTRL_TYPE_CPR3;
+ ctrl->supports_hw_closed_loop = true;
+ ctrl->use_hw_closed_loop = of_property_read_bool(ctrl->dev->of_node,
+ "qcom,cpr-hw-closed-loop");
+
+ if (ctrl->mem_acc_regulator) {
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,mem-acc-supply-threshold-voltage",
+ &ctrl->mem_acc_threshold_volt);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,mem-acc-supply-threshold-voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ ctrl->mem_acc_threshold_volt =
+ CPR3_ROUND(ctrl->mem_acc_threshold_volt,
+ ctrl->step_volt);
+
+ rc = of_property_read_u32_array(ctrl->dev->of_node,
+ "qcom,mem-acc-supply-corner-map",
+ &ctrl->mem_acc_corner_map[CPR3_MEM_ACC_LOW_CORNER],
+ CPR3_MEM_ACC_CORNERS);
+ if (rc) {
+ cpr3_err(ctrl, "error reading qcom,mem-acc-supply-corner-map, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int cpr3_hmss_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 cpr3_hmss_regulator_resume(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_resume(ctrl);
+}
+
+/* Data corresponds to the SoC revision */
+static const struct of_device_id cpr_regulator_match_table[] = {
+ {
+ .compatible = "qcom,cpr3-msm8996-v1-hmss-regulator",
+ .data = (void *)(uintptr_t)1
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996-v2-hmss-regulator",
+ .data = (void *)(uintptr_t)2
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996-v3-hmss-regulator",
+ .data = (void *)(uintptr_t)3
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996-hmss-regulator",
+ .data = (void *)(uintptr_t)3
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996pro-hmss-regulator",
+ .data = (void *)(uintptr_t)MSM8996PRO_SOC_ID,
+ },
+ {}
+};
+
+static int cpr3_hmss_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct cpr3_controller *ctrl;
+ struct cpr3_regulator *vreg;
+ int i, j, 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;
+
+ 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;
+ }
+
+ match = of_match_node(cpr_regulator_match_table, dev->of_node);
+ if (match)
+ ctrl->soc_revision = (uintptr_t)match->data;
+ else
+ cpr3_err(ctrl, "could not find compatible string match\n");
+
+ rc = cpr3_map_fuse_base(ctrl, pdev);
+ if (rc) {
+ cpr3_err(ctrl, "could not map fuse base address\n");
+ return rc;
+ }
+
+ rc = cpr3_allocate_threads(ctrl, MSM8996_HMSS_POWER_CLUSTER_THREAD_ID,
+ MSM8996_HMSS_PERFORMANCE_CLUSTER_THREAD_ID);
+ 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, "thread nodes are missing\n");
+ return -EINVAL;
+ }
+
+ rc = cpr3_hmss_init_controller(ctrl);
+ if (rc) {
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(ctrl, "failed to initialize CPR controller parameters, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < ctrl->thread_count; i++) {
+ rc = cpr3_hmss_init_thread(&ctrl->thread[i]);
+ if (rc) {
+ cpr3_err(ctrl, "thread %u initialization failed, rc=%d\n",
+ ctrl->thread[i].thread_id, rc);
+ return rc;
+ }
+
+ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
+ vreg = &ctrl->thread[i].vreg[j];
+
+ rc = cpr3_hmss_init_regulator(vreg);
+ if (rc) {
+ cpr3_err(vreg, "regulator initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ rc = cpr3_hmss_init_aging(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize aging configurations, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, ctrl);
+
+ return cpr3_regulator_register(pdev, ctrl);
+}
+
+static int cpr3_hmss_regulator_remove(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_unregister(ctrl);
+}
+
+static struct platform_driver cpr3_hmss_regulator_driver = {
+ .driver = {
+ .name = "qcom,cpr3-hmss-regulator",
+ .of_match_table = cpr_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = cpr3_hmss_regulator_probe,
+ .remove = cpr3_hmss_regulator_remove,
+ .suspend = cpr3_hmss_regulator_suspend,
+ .resume = cpr3_hmss_regulator_resume,
+};
+
+static int cpr_regulator_init(void)
+{
+ return platform_driver_register(&cpr3_hmss_regulator_driver);
+}
+
+static void cpr_regulator_exit(void)
+{
+ platform_driver_unregister(&cpr3_hmss_regulator_driver);
+}
+
+MODULE_DESCRIPTION("CPR3 HMSS regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(cpr_regulator_init);
+module_exit(cpr_regulator_exit);
diff --git a/drivers/regulator/cpr3-mmss-regulator.c b/drivers/regulator/cpr3-mmss-regulator.c
new file mode 100644
index 0000000..41032dd
--- /dev/null
+++ b/drivers/regulator/cpr3-mmss-regulator.c
@@ -0,0 +1,1251 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#include "cpr3-regulator.h"
+
+#define MSM8996_MMSS_FUSE_CORNERS 4
+
+/**
+ * struct cpr3_msm8996_mmss_fuses - MMSS specific fuse data for MSM8996
+ * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
+ * for each fuse corner (raw, not converted to a voltage)
+ * @offset_voltage: The closed-loop voltage margin adjustment fuse parameter
+ * value for each fuse corner (raw, not converted to a
+ * voltage)
+ * @speed_bin: Graphics processor speed bin fuse parameter value for
+ * the given chip
+ * @cpr_fusing_rev: CPR fusing revision fuse parameter value
+ * @limitation: CPR limitation select fuse parameter value
+ * @aging_init_quot_diff: Initial quotient difference between CPR aging
+ * min and max sensors measured at time of manufacturing
+ * @force_highest_corner: Flag indicating that all corners must operate
+ * at the voltage of the highest corner. This is
+ * applicable to MSM8998 only.
+ *
+ * This struct holds the values for all of the fuses read from memory.
+ */
+struct cpr3_msm8996_mmss_fuses {
+ u64 init_voltage[MSM8996_MMSS_FUSE_CORNERS];
+ u64 offset_voltage[MSM8996_MMSS_FUSE_CORNERS];
+ u64 speed_bin;
+ u64 cpr_fusing_rev;
+ u64 limitation;
+ u64 aging_init_quot_diff;
+ u64 force_highest_corner;
+};
+
+/* Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 */
+#define CPR3_MSM8996_MMSS_FUSE_COMBO_COUNT 8
+
+/*
+ * Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 with speed bin fuse = 0.
+ * Fuse combos 8 - 15 map to CPR fusing revision 0 - 7 with speed bin fuse = 1.
+ */
+#define CPR3_MSM8996PRO_MMSS_FUSE_COMBO_COUNT 16
+
+/* Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 */
+#define CPR3_MSM8998_MMSS_FUSE_COMBO_COUNT 8
+
+/*
+ * MSM8996 MMSS 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 const struct cpr3_fuse_param
+msm8996_mmss_init_voltage_param[MSM8996_MMSS_FUSE_CORNERS][2] = {
+ {{63, 55, 59}, {} },
+ {{63, 50, 54}, {} },
+ {{63, 45, 49}, {} },
+ {{63, 40, 44}, {} },
+};
+
+static const struct cpr3_fuse_param msm8996_cpr_fusing_rev_param[] = {
+ {39, 48, 50},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8996_cpr_limitation_param[] = {
+ {41, 31, 32},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8996_mmss_aging_init_quot_diff_param[] = {
+ {68, 26, 31},
+ {},
+};
+
+/* Offset voltages are defined for SVS and Turbo fuse corners only */
+static const struct cpr3_fuse_param
+msm8996_mmss_offset_voltage_param[MSM8996_MMSS_FUSE_CORNERS][2] = {
+ {{} },
+ {{66, 42, 44}, {} },
+ {{} },
+ {{64, 58, 61}, {} },
+};
+
+static const struct cpr3_fuse_param msm8996pro_mmss_speed_bin_param[] = {
+ {39, 60, 61},
+ {},
+};
+
+/* MSM8998 MMSS fuse parameter locations: */
+static const struct cpr3_fuse_param
+msm8998_mmss_init_voltage_param[MSM8996_MMSS_FUSE_CORNERS][2] = {
+ {{65, 39, 43}, {} },
+ {{65, 34, 38}, {} },
+ {{65, 29, 33}, {} },
+ {{65, 24, 28}, {} },
+};
+
+static const struct cpr3_fuse_param msm8998_cpr_fusing_rev_param[] = {
+ {39, 48, 50},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8998_cpr_limitation_param[] = {
+ {41, 46, 47},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8998_mmss_aging_init_quot_diff_param[] = {
+ {65, 60, 63},
+ {66, 0, 3},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8998_mmss_offset_voltage_param[MSM8996_MMSS_FUSE_CORNERS][2] = {
+ {{65, 56, 59}, {} },
+ {{65, 52, 55}, {} },
+ {{65, 48, 51}, {} },
+ {{65, 44, 47}, {} },
+};
+
+static const struct cpr3_fuse_param
+msm8998_cpr_force_highest_corner_param[] = {
+ {100, 45, 45},
+ {},
+};
+
+#define MSM8996PRO_SOC_ID 4
+#define MSM8998_V1_SOC_ID 5
+#define MSM8998_V2_SOC_ID 6
+
+/*
+ * Some initial msm8996 parts cannot be used in a meaningful way by software.
+ * Other parts can only be used when operating with CPR disabled (i.e. at the
+ * fused open-loop voltage) when no voltage interpolation is applied. A fuse
+ * parameter is provided so that software can properly handle these limitations.
+ */
+enum msm8996_cpr_limitation {
+ MSM8996_CPR_LIMITATION_NONE = 0,
+ MSM8996_CPR_LIMITATION_UNSUPPORTED = 2,
+ MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION = 3,
+};
+
+/* Additional MSM8996 specific data: */
+
+/* Open loop voltage fuse reference voltages in microvolts */
+static const int msm8996_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
+ 670000,
+ 745000,
+ 905000,
+ 1015000,
+};
+
+static const int msm8996pro_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
+ 670000,
+ 745000,
+ 905000,
+ 1065000,
+};
+
+static const int msm8998_v1_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
+ 528000,
+ 656000,
+ 812000,
+ 932000,
+};
+
+static const int
+msm8998_v1_rev0_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
+ 632000,
+ 768000,
+ 896000,
+ 1032000,
+};
+
+static const int msm8998_v2_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
+ 516000,
+ 628000,
+ 752000,
+ 924000,
+};
+
+static const int
+msm8998_v2_rev0_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
+ 616000,
+ 740000,
+ 828000,
+ 1024000,
+};
+
+#define MSM8996_MMSS_FUSE_STEP_VOLT 10000
+#define MSM8996_MMSS_OFFSET_FUSE_STEP_VOLT 10000
+#define MSM8996_MMSS_VOLTAGE_FUSE_SIZE 5
+#define MSM8996_MMSS_MIN_VOLTAGE_FUSE_VAL 0x1F
+#define MSM8996_MMSS_AGING_INIT_QUOT_DIFF_SCALE 2
+#define MSM8996_MMSS_AGING_INIT_QUOT_DIFF_SIZE 6
+
+#define MSM8996_MMSS_CPR_SENSOR_COUNT 35
+
+#define MSM8996_MMSS_CPR_CLOCK_RATE 19200000
+
+#define MSM8996_MMSS_AGING_SENSOR_ID 29
+#define MSM8996_MMSS_AGING_BYPASS_MASK0 (GENMASK(23, 0))
+
+#define MSM8998_MMSS_AGING_INIT_QUOT_DIFF_SCALE 1
+#define MSM8998_MMSS_AGING_INIT_QUOT_DIFF_SIZE 8
+
+#define MSM8998_MMSS_CPR_SENSOR_COUNT 35
+
+#define MSM8998_MMSS_AGING_SENSOR_ID 29
+#define MSM8998_MMSS_AGING_BYPASS_MASK0 (GENMASK(23, 0))
+
+#define MSM8998_MMSS_MAX_TEMP_POINTS 3
+#define MSM8998_MMSS_TEMP_SENSOR_ID_START 12
+#define MSM8998_MMSS_TEMP_SENSOR_ID_END 13
+
+/*
+ * Some initial msm8998 parts cannot be operated at low voltages. The
+ * open-loop voltage fuses are reused to identify these parts so that software
+ * can properly handle the limitation. 0xF means that the next higher fuse
+ * corner should be used. 0xE means that the next higher fuse corner which
+ * does not have a voltage limitation should be used.
+ */
+enum msm8998_cpr_partial_binning {
+ MSM8998_CPR_PARTIAL_BINNING_NEXT_CORNER = 0xF,
+ MSM8998_CPR_PARTIAL_BINNING_SAFE_CORNER = 0xE,
+};
+
+/*
+ * The partial binning open-loop voltage fuse values only apply to the lowest
+ * two fuse corners (0 and 1, i.e. MinSVS and SVS).
+ */
+#define MSM8998_CPR_PARTIAL_BINNING_MAX_FUSE_CORNER 1
+
+static inline bool cpr3_ctrl_is_msm8998(const struct cpr3_controller *ctrl)
+{
+ return ctrl->soc_revision == MSM8998_V1_SOC_ID ||
+ ctrl->soc_revision == MSM8998_V2_SOC_ID;
+}
+
+/**
+ * cpr3_msm8996_mmss_read_fuse_data() - load MMSS specific fuse parameter values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function allocates a cpr3_msm8996_mmss_fuses struct, fills it with
+ * values read out of hardware fuses, and finally copies common fuse values
+ * into the regulator struct.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ struct cpr3_msm8996_mmss_fuses *fuse;
+ int i, rc, combo_max;
+
+ fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
+ if (!fuse)
+ return -ENOMEM;
+
+ if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID) {
+ rc = cpr3_read_fuse_param(base, msm8996pro_mmss_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,
+ cpr3_ctrl_is_msm8998(vreg->thread->ctrl)
+ ? msm8998_cpr_fusing_rev_param
+ : msm8996_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,
+ cpr3_ctrl_is_msm8998(vreg->thread->ctrl)
+ ? msm8998_cpr_limitation_param
+ : msm8996_cpr_limitation_param,
+ &fuse->limitation);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read CPR limitation fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+ cpr3_info(vreg, "CPR limitation = %s\n",
+ fuse->limitation == MSM8996_CPR_LIMITATION_UNSUPPORTED
+ ? "unsupported chip" : fuse->limitation
+ == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION
+ ? "CPR disabled and no interpolation" : "none");
+
+ rc = cpr3_read_fuse_param(base,
+ cpr3_ctrl_is_msm8998(vreg->thread->ctrl)
+ ? msm8998_mmss_aging_init_quot_diff_param
+ : msm8996_mmss_aging_init_quot_diff_param,
+ &fuse->aging_init_quot_diff);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read aging initial quotient difference fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < MSM8996_MMSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ cpr3_ctrl_is_msm8998(vreg->thread->ctrl)
+ ? msm8998_mmss_init_voltage_param[i]
+ : msm8996_mmss_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,
+ cpr3_ctrl_is_msm8998(vreg->thread->ctrl)
+ ? msm8998_mmss_offset_voltage_param[i]
+ : msm8996_mmss_offset_voltage_param[i],
+ &fuse->offset_voltage[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d offset voltage fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+
+ if (cpr3_ctrl_is_msm8998(vreg->thread->ctrl)) {
+ rc = cpr3_read_fuse_param(base,
+ msm8998_cpr_force_highest_corner_param,
+ &fuse->force_highest_corner);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read CPR force highest corner fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+ if (fuse->force_highest_corner)
+ cpr3_info(vreg, "Fusing requires all operation at the highest corner\n");
+ }
+
+ if (cpr3_ctrl_is_msm8998(vreg->thread->ctrl)) {
+ combo_max = CPR3_MSM8998_MMSS_FUSE_COMBO_COUNT;
+ vreg->fuse_combo = fuse->cpr_fusing_rev;
+ } else if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID) {
+ combo_max = CPR3_MSM8996PRO_MMSS_FUSE_COMBO_COUNT;
+ vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
+ } else {
+ combo_max = CPR3_MSM8996_MMSS_FUSE_COMBO_COUNT;
+ vreg->fuse_combo = fuse->cpr_fusing_rev;
+ }
+
+ if (vreg->fuse_combo >= combo_max) {
+ cpr3_err(vreg, "invalid CPR fuse combo = %d found, not in range 0 - %d\n",
+ vreg->fuse_combo, combo_max - 1);
+ return -EINVAL;
+ }
+
+ vreg->speed_bin_fuse = fuse->speed_bin;
+ vreg->cpr_rev_fuse = fuse->cpr_fusing_rev;
+ vreg->fuse_corner_count = MSM8996_MMSS_FUSE_CORNERS;
+ vreg->platform_fuses = fuse;
+
+ return 0;
+}
+
+/**
+ * cpr3_mmss_parse_corner_data() - parse MMSS corner data from device tree
+ * properties of the regulator's device node
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_mmss_parse_corner_data(struct cpr3_regulator *vreg)
+{
+ int i, rc;
+ u32 *temp;
+
+ rc = cpr3_parse_common_corner_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
+ return rc;
+ }
+
+ temp = kcalloc(vreg->corner_count * CPR3_RO_COUNT, sizeof(*temp),
+ GFP_KERNEL);
+ if (!temp)
+ return -ENOMEM;
+
+ rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-target-quotients",
+ CPR3_RO_COUNT, temp);
+ if (rc) {
+ cpr3_err(vreg, "could not load target quotients, rc=%d\n", rc);
+ goto done;
+ }
+
+ for (i = 0; i < vreg->corner_count; i++)
+ memcpy(vreg->corner[i].target_quot, &temp[i * CPR3_RO_COUNT],
+ sizeof(*temp) * CPR3_RO_COUNT);
+
+done:
+ kfree(temp);
+ return rc;
+}
+
+/**
+ * cpr3_msm8996_mmss_adjust_target_quotients() - adjust the target quotients
+ * for each corner according to device tree values and fuse values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_msm8996_mmss_adjust_target_quotients(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_mmss_fuses *fuse = vreg->platform_fuses;
+ const struct cpr3_fuse_param (*offset_param)[2];
+ int *volt_offset;
+ int i, fuse_len, rc = 0;
+
+ volt_offset = kcalloc(vreg->fuse_corner_count, sizeof(*volt_offset),
+ GFP_KERNEL);
+ if (!volt_offset)
+ return -ENOMEM;
+
+ offset_param = cpr3_ctrl_is_msm8998(vreg->thread->ctrl)
+ ? msm8998_mmss_offset_voltage_param
+ : msm8996_mmss_offset_voltage_param;
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ fuse_len = offset_param[i][0].bit_end + 1
+ - offset_param[i][0].bit_start;
+ volt_offset[i] = cpr3_convert_open_loop_voltage_fuse(
+ 0, MSM8996_MMSS_OFFSET_FUSE_STEP_VOLT,
+ fuse->offset_voltage[i], fuse_len);
+ if (volt_offset[i])
+ cpr3_info(vreg, "fuse_corner[%d] offset=%7d uV\n",
+ i, volt_offset[i]);
+ }
+
+ rc = cpr3_adjust_target_quotients(vreg, volt_offset);
+ if (rc)
+ cpr3_err(vreg, "adjust target quotients failed, rc=%d\n", rc);
+
+ kfree(volt_offset);
+ return rc;
+}
+
+/**
+ * cpr3_msm8996_mmss_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 both device tree and in
+ * hardware fuses, 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_msm8996_mmss_calculate_open_loop_voltages(
+ struct cpr3_regulator *vreg)
+{
+ struct device_node *node = vreg->of_node;
+ struct cpr3_msm8996_mmss_fuses *fuse = vreg->platform_fuses;
+ bool is_msm8998 = cpr3_ctrl_is_msm8998(vreg->thread->ctrl);
+ int rc = 0;
+ bool allow_interpolation;
+ u64 freq_low, volt_low, freq_high, volt_high, volt_init;
+ int i, j;
+ const int *ref_volt;
+ 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;
+ }
+
+ if (vreg->thread->ctrl->soc_revision == MSM8998_V2_SOC_ID
+ && fuse->cpr_fusing_rev == 0)
+ ref_volt = msm8998_v2_rev0_mmss_fuse_ref_volt;
+ else if (vreg->thread->ctrl->soc_revision == MSM8998_V2_SOC_ID)
+ ref_volt = msm8998_v2_mmss_fuse_ref_volt;
+ else if (vreg->thread->ctrl->soc_revision == MSM8998_V1_SOC_ID
+ && fuse->cpr_fusing_rev == 0)
+ ref_volt = msm8998_v1_rev0_mmss_fuse_ref_volt;
+ else if (vreg->thread->ctrl->soc_revision == MSM8998_V1_SOC_ID)
+ ref_volt = msm8998_v1_mmss_fuse_ref_volt;
+ else if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID)
+ ref_volt = msm8996pro_mmss_fuse_ref_volt;
+ else
+ ref_volt = msm8996_mmss_fuse_ref_volt;
+
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ volt_init = fuse->init_voltage[i];
+ /*
+ * Handle partial binning on MSM8998 where the initial voltage
+ * fuse is reused as a flag for partial binning needs. Set the
+ * open-loop voltage to the minimum possible value so that it
+ * does not result in higher fuse corners getting forced to
+ * higher open-loop voltages after monotonicity enforcement.
+ */
+ if (is_msm8998 &&
+ (volt_init == MSM8998_CPR_PARTIAL_BINNING_NEXT_CORNER ||
+ volt_init == MSM8998_CPR_PARTIAL_BINNING_SAFE_CORNER) &&
+ i <= MSM8998_CPR_PARTIAL_BINNING_MAX_FUSE_CORNER)
+ volt_init = MSM8996_MMSS_MIN_VOLTAGE_FUSE_VAL;
+
+ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(ref_volt[i],
+ MSM8996_MMSS_FUSE_STEP_VOLT, volt_init,
+ MSM8996_MMSS_VOLTAGE_FUSE_SIZE);
+ cpr3_info(vreg, "fuse_corner[%d] open-loop=%7d uV\n",
+ i, fuse_volt[i]);
+ }
+
+ 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_debug(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 (fuse->limitation == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION)
+ allow_interpolation = false;
+
+ 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);
+ }
+
+ kfree(fuse_volt);
+ kfree(fmax_corner);
+ return rc;
+}
+
+/**
+ * cpr3_msm8998_partial_binning_override() - override the voltage and quotient
+ * settings for low corners based upon the special partial binning
+ * open-loop voltage fuse values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Some parts are not able to operate at low voltages. The partial binning
+ * open-loop voltage fuse values specify if a given part has such limitations.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_msm8998_partial_binning_override(struct cpr3_regulator *vreg)
+{
+ struct cpr3_msm8996_mmss_fuses *fuse = vreg->platform_fuses;
+ u64 next = MSM8998_CPR_PARTIAL_BINNING_NEXT_CORNER;
+ u64 safe = MSM8998_CPR_PARTIAL_BINNING_SAFE_CORNER;
+ u32 proc_freq;
+ struct cpr3_corner *corner;
+ struct cpr3_corner *safe_corner;
+ int i, j, low, high, safe_fuse_corner, max_fuse_corner;
+
+ if (!cpr3_ctrl_is_msm8998(vreg->thread->ctrl))
+ return 0;
+
+ /* Handle the force highest corner fuse. */
+ if (fuse->force_highest_corner) {
+ cpr3_info(vreg, "overriding CPR parameters for corners 0 to %d with quotients and voltages of corner %d\n",
+ vreg->corner_count - 2, vreg->corner_count - 1);
+ corner = &vreg->corner[vreg->corner_count - 1];
+ for (i = 0; i < vreg->corner_count - 1; i++) {
+ proc_freq = vreg->corner[i].proc_freq;
+ vreg->corner[i] = *corner;
+ vreg->corner[i].proc_freq = proc_freq;
+ }
+
+ /*
+ * Return since the potential partial binning fuse values are
+ * superceded by the force highest corner fuse value.
+ */
+ return 0;
+ }
+
+ /*
+ * Allow up to the max corner which can be fused with partial
+ * binning values.
+ */
+ max_fuse_corner = min(MSM8998_CPR_PARTIAL_BINNING_MAX_FUSE_CORNER,
+ vreg->fuse_corner_count - 2);
+
+ for (i = 0; i <= max_fuse_corner; i++) {
+ /* Determine which higher corners to override with (if any). */
+ if (fuse->init_voltage[i] != next
+ && fuse->init_voltage[i] != safe)
+ continue;
+
+ for (j = i + 1; j <= max_fuse_corner; j++)
+ if (fuse->init_voltage[j] != next
+ && fuse->init_voltage[j] != safe)
+ break;
+ safe_fuse_corner = j;
+
+ j = fuse->init_voltage[i] == next ? i + 1 : safe_fuse_corner;
+
+ low = i > 0 ? vreg->fuse_corner_map[i] : 0;
+ high = vreg->fuse_corner_map[i + 1] - 1;
+
+ cpr3_info(vreg, "overriding CPR parameters for corners %d to %d with quotients of corner %d and voltages of corner %d\n",
+ low, high, vreg->fuse_corner_map[j],
+ vreg->fuse_corner_map[safe_fuse_corner]);
+
+ corner = &vreg->corner[vreg->fuse_corner_map[j]];
+ safe_corner
+ = &vreg->corner[vreg->fuse_corner_map[safe_fuse_corner]];
+
+ for (j = low; j <= high; j++) {
+ proc_freq = vreg->corner[j].proc_freq;
+ vreg->corner[j] = *corner;
+ vreg->corner[j].proc_freq = proc_freq;
+
+ vreg->corner[j].floor_volt
+ = safe_corner->floor_volt;
+ vreg->corner[j].ceiling_volt
+ = safe_corner->ceiling_volt;
+ vreg->corner[j].open_loop_volt
+ = safe_corner->open_loop_volt;
+ vreg->corner[j].abs_ceiling_volt
+ = safe_corner->abs_ceiling_volt;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * cpr3_mmss_print_settings() - print out MMSS CPR configuration settings into
+ * the kernel log for debugging purposes
+ * @vreg: Pointer to the CPR3 regulator
+ */
+static void cpr3_mmss_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);
+ }
+}
+
+/**
+ * cpr3_mmss_init_aging() - perform MMSS CPR3 controller specific
+ * aging initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_mmss_init_aging(struct cpr3_controller *ctrl)
+{
+ struct cpr3_msm8996_mmss_fuses *fuse;
+ struct cpr3_regulator *vreg;
+ u32 aging_ro_scale;
+ int rc;
+
+ vreg = &ctrl->thread[0].vreg[0];
+
+ ctrl->aging_required = vreg->aging_allowed;
+ fuse = vreg->platform_fuses;
+
+ if (!ctrl->aging_required || !fuse)
+ return 0;
+
+ rc = cpr3_parse_array_property(vreg, "qcom,cpr-aging-ro-scaling-factor",
+ 1, &aging_ro_scale);
+ if (rc)
+ return rc;
+
+ if (aging_ro_scale == 0) {
+ cpr3_err(ctrl, "aging RO scaling factor is invalid: %u\n",
+ aging_ro_scale);
+ return -EINVAL;
+ }
+
+ ctrl->aging_vdd_mode = REGULATOR_MODE_NORMAL;
+ ctrl->aging_complete_vdd_mode = REGULATOR_MODE_IDLE;
+
+ ctrl->aging_sensor_count = 1;
+ ctrl->aging_sensor = kzalloc(sizeof(*ctrl->aging_sensor), GFP_KERNEL);
+ if (!ctrl->aging_sensor)
+ return -ENOMEM;
+
+ ctrl->aging_sensor->ro_scale = aging_ro_scale;
+
+ if (cpr3_ctrl_is_msm8998(ctrl)) {
+ ctrl->aging_sensor->sensor_id = MSM8998_MMSS_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0]
+ = MSM8998_MMSS_AGING_BYPASS_MASK0;
+ ctrl->aging_sensor->init_quot_diff
+ = cpr3_convert_open_loop_voltage_fuse(0,
+ MSM8998_MMSS_AGING_INIT_QUOT_DIFF_SCALE,
+ fuse->aging_init_quot_diff,
+ MSM8998_MMSS_AGING_INIT_QUOT_DIFF_SIZE);
+ } else {
+ ctrl->aging_sensor->sensor_id = MSM8996_MMSS_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0]
+ = MSM8996_MMSS_AGING_BYPASS_MASK0;
+ ctrl->aging_sensor->init_quot_diff
+ = cpr3_convert_open_loop_voltage_fuse(0,
+ MSM8996_MMSS_AGING_INIT_QUOT_DIFF_SCALE,
+ fuse->aging_init_quot_diff,
+ MSM8996_MMSS_AGING_INIT_QUOT_DIFF_SIZE);
+ }
+
+ cpr3_debug(ctrl, "sensor %u aging init quotient diff = %d, aging RO scale = %u QUOT/V\n",
+ ctrl->aging_sensor->sensor_id,
+ ctrl->aging_sensor->init_quot_diff,
+ ctrl->aging_sensor->ro_scale);
+
+ return 0;
+}
+
+/**
+ * cpr3_mmss_init_thread() - perform all 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_mmss_init_thread(struct cpr3_thread *thread)
+{
+ struct cpr3_regulator *vreg = &thread->vreg[0];
+ struct cpr3_msm8996_mmss_fuses *fuse;
+ int rc;
+
+ rc = cpr3_parse_common_thread_data(thread);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR thread data from device tree, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_msm8996_mmss_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;
+ if (fuse->limitation == MSM8996_CPR_LIMITATION_UNSUPPORTED) {
+ cpr3_err(vreg, "this chip requires an unsupported voltage\n");
+ return -EPERM;
+ } else if (fuse->limitation
+ == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION) {
+ thread->ctrl->cpr_allowed_hw = false;
+ }
+
+ rc = cpr3_mmss_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_msm8996_mmss_adjust_target_quotients(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to adjust target quotients, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_msm8996_mmss_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;
+ }
+
+ if (cpr3_ctrl_is_msm8998(thread->ctrl)) {
+ rc = cpr4_parse_core_count_temp_voltage_adj(vreg, false);
+ if (rc) {
+ cpr3_err(vreg, "unable to parse temperature based voltage adjustments, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = cpr3_msm8998_partial_binning_override(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to override CPR parameters based on partial binning fuse values, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cpr3_mmss_print_settings(vreg);
+
+ return 0;
+}
+
+/**
+ * cpr4_mmss_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_mmss_parse_temp_adj_properties(struct cpr3_controller *ctrl)
+{
+ struct device_node *of_node = ctrl->dev->of_node;
+ int rc, len, temp_point_count;
+
+ if (!of_find_property(of_node, "qcom,cpr-temp-point-map", &len))
+ return 0;
+
+ temp_point_count = len / sizeof(u32);
+ if (temp_point_count <= 0
+ || temp_point_count > MSM8998_MMSS_MAX_TEMP_POINTS) {
+ cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
+ temp_point_count, MSM8998_MMSS_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;
+ }
+
+ /*
+ * 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;
+
+ 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 = MSM8998_MMSS_TEMP_SENSOR_ID_START;
+ ctrl->temp_sensor_id_end = MSM8998_MMSS_TEMP_SENSOR_ID_END;
+ ctrl->allow_temp_adj = true;
+
+ return rc;
+}
+
+/**
+ * cpr3_mmss_init_controller() - perform MMSS CPR3 controller specific
+ * initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_mmss_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;
+ }
+
+ if (cpr3_ctrl_is_msm8998(ctrl)) {
+ rc = cpr4_mmss_parse_temp_adj_properties(ctrl);
+ if (rc)
+ return rc;
+ }
+
+ ctrl->sensor_count = cpr3_ctrl_is_msm8998(ctrl)
+ ? MSM8998_MMSS_CPR_SENSOR_COUNT
+ : MSM8996_MMSS_CPR_SENSOR_COUNT;
+
+ /*
+ * MMSS only has one thread (0) 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->cpr_clock_rate = MSM8996_MMSS_CPR_CLOCK_RATE;
+ ctrl->ctrl_type = cpr3_ctrl_is_msm8998(ctrl)
+ ? CPR_CTRL_TYPE_CPR4 : CPR_CTRL_TYPE_CPR3;
+
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
+ /*
+ * 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;
+ }
+
+ ctrl->iface_clk = devm_clk_get(ctrl->dev, "iface_clk");
+ if (IS_ERR(ctrl->iface_clk)) {
+ rc = PTR_ERR(ctrl->iface_clk);
+ if (cpr3_ctrl_is_msm8998(ctrl)) {
+ /* iface_clk is optional for msm8998 */
+ ctrl->iface_clk = NULL;
+ } else if (rc == -EPROBE_DEFER) {
+ return rc;
+ } else {
+ cpr3_err(ctrl, "unable to request interface clock, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ ctrl->bus_clk = devm_clk_get(ctrl->dev, "bus_clk");
+ if (IS_ERR(ctrl->bus_clk)) {
+ rc = PTR_ERR(ctrl->bus_clk);
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(ctrl, "unable request bus clock, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cpr3_mmss_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 cpr3_mmss_regulator_resume(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_resume(ctrl);
+}
+
+/* Data corresponds to the SoC revision */
+static const struct of_device_id cpr_regulator_match_table[] = {
+ {
+ .compatible = "qcom,cpr3-msm8996-v1-mmss-regulator",
+ .data = (void *)(uintptr_t)1,
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996-v2-mmss-regulator",
+ .data = (void *)(uintptr_t)2,
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996-v3-mmss-regulator",
+ .data = (void *)(uintptr_t)3,
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996-mmss-regulator",
+ .data = (void *)(uintptr_t)3,
+ },
+ {
+ .compatible = "qcom,cpr3-msm8996pro-mmss-regulator",
+ .data = (void *)(uintptr_t)MSM8996PRO_SOC_ID,
+ },
+ {
+ .compatible = "qcom,cpr4-msm8998-v1-mmss-regulator",
+ .data = (void *)(uintptr_t)MSM8998_V1_SOC_ID,
+ },
+ {
+ .compatible = "qcom,cpr4-msm8998-v2-mmss-regulator",
+ .data = (void *)(uintptr_t)MSM8998_V2_SOC_ID,
+ },
+ {
+ .compatible = "qcom,cpr4-msm8998-mmss-regulator",
+ .data = (void *)(uintptr_t)MSM8998_V2_SOC_ID,
+ },
+ {}
+};
+
+static int cpr3_mmss_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct cpr3_controller *ctrl;
+ int 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;
+
+ 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;
+ }
+
+ match = of_match_node(cpr_regulator_match_table, dev->of_node);
+ if (match)
+ ctrl->soc_revision = (uintptr_t)match->data;
+ else
+ cpr3_err(ctrl, "could not find compatible string match\n");
+
+ rc = cpr3_map_fuse_base(ctrl, pdev);
+ if (rc) {
+ cpr3_err(ctrl, "could not map fuse base address\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;
+ } else if (ctrl->thread[0].vreg_count != 1) {
+ cpr3_err(ctrl, "expected 1 regulator but found %d\n",
+ ctrl->thread[0].vreg_count);
+ return -EINVAL;
+ }
+
+ rc = cpr3_mmss_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_mmss_init_thread(&ctrl->thread[0]);
+ if (rc) {
+ cpr3_err(&ctrl->thread[0].vreg[0], "thread initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_mem_acc_init(&ctrl->thread[0].vreg[0]);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize mem-acc configuration, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_mmss_init_aging(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize aging configurations, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, ctrl);
+
+ return cpr3_regulator_register(pdev, ctrl);
+}
+
+static int cpr3_mmss_regulator_remove(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_unregister(ctrl);
+}
+
+static struct platform_driver cpr3_mmss_regulator_driver = {
+ .driver = {
+ .name = "qcom,cpr3-mmss-regulator",
+ .of_match_table = cpr_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = cpr3_mmss_regulator_probe,
+ .remove = cpr3_mmss_regulator_remove,
+ .suspend = cpr3_mmss_regulator_suspend,
+ .resume = cpr3_mmss_regulator_resume,
+};
+
+static int cpr_regulator_init(void)
+{
+ return platform_driver_register(&cpr3_mmss_regulator_driver);
+}
+
+static void cpr_regulator_exit(void)
+{
+ platform_driver_unregister(&cpr3_mmss_regulator_driver);
+}
+
+MODULE_DESCRIPTION("CPR3 MMSS regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(cpr_regulator_init);
+module_exit(cpr_regulator_exit);
diff --git a/drivers/regulator/cpr3-regulator.c b/drivers/regulator/cpr3-regulator.c
new file mode 100644
index 0000000..c51ed182b
--- /dev/null
+++ b/drivers/regulator/cpr3-regulator.c
@@ -0,0 +1,6492 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/ktime.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/msm-ldo-regulator.h>
+
+#include <soc/qcom/spm.h>
+
+#include "cpr3-regulator.h"
+
+#define CPR3_REGULATOR_CORNER_INVALID (-1)
+#define CPR3_RO_MASK GENMASK(CPR3_RO_COUNT - 1, 0)
+
+/* CPR3 registers */
+#define CPR3_REG_CPR_CTL 0x4
+#define CPR3_CPR_CTL_LOOP_EN_MASK BIT(0)
+#define CPR3_CPR_CTL_LOOP_ENABLE BIT(0)
+#define CPR3_CPR_CTL_LOOP_DISABLE 0
+#define CPR3_CPR_CTL_IDLE_CLOCKS_MASK GENMASK(5, 1)
+#define CPR3_CPR_CTL_IDLE_CLOCKS_SHIFT 1
+#define CPR3_CPR_CTL_COUNT_MODE_MASK GENMASK(7, 6)
+#define CPR3_CPR_CTL_COUNT_MODE_SHIFT 6
+#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MIN 0
+#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_MAX 1
+#define CPR3_CPR_CTL_COUNT_MODE_STAGGERED 2
+#define CPR3_CPR_CTL_COUNT_MODE_ALL_AT_ONCE_AGE 3
+#define CPR3_CPR_CTL_COUNT_REPEAT_MASK GENMASK(31, 9)
+#define CPR3_CPR_CTL_COUNT_REPEAT_SHIFT 9
+
+#define CPR3_REG_CPR_STATUS 0x8
+#define CPR3_CPR_STATUS_BUSY_MASK BIT(0)
+#define CPR3_CPR_STATUS_AGING_MEASUREMENT_MASK BIT(1)
+
+/*
+ * This register is not present on controllers that support HW closed-loop
+ * except CPR4 APSS controller.
+ */
+#define CPR3_REG_CPR_TIMER_AUTO_CONT 0xC
+
+#define CPR3_REG_CPR_STEP_QUOT 0x14
+#define CPR3_CPR_STEP_QUOT_MIN_MASK GENMASK(5, 0)
+#define CPR3_CPR_STEP_QUOT_MIN_SHIFT 0
+#define CPR3_CPR_STEP_QUOT_MAX_MASK GENMASK(11, 6)
+#define CPR3_CPR_STEP_QUOT_MAX_SHIFT 6
+
+#define CPR3_REG_GCNT(ro) (0xA0 + 0x4 * (ro))
+
+#define CPR3_REG_SENSOR_BYPASS_WRITE(sensor) (0xE0 + 0x4 * ((sensor) / 32))
+#define CPR3_REG_SENSOR_BYPASS_WRITE_BANK(bank) (0xE0 + 0x4 * (bank))
+
+#define CPR3_REG_SENSOR_MASK_WRITE(sensor) (0x120 + 0x4 * ((sensor) / 32))
+#define CPR3_REG_SENSOR_MASK_WRITE_BANK(bank) (0x120 + 0x4 * (bank))
+#define CPR3_REG_SENSOR_MASK_READ(sensor) (0x140 + 0x4 * ((sensor) / 32))
+
+#define CPR3_REG_SENSOR_OWNER(sensor) (0x200 + 0x4 * (sensor))
+
+#define CPR3_REG_CONT_CMD 0x800
+#define CPR3_CONT_CMD_ACK 0x1
+#define CPR3_CONT_CMD_NACK 0x0
+
+#define CPR3_REG_THRESH(thread) (0x808 + 0x440 * (thread))
+#define CPR3_THRESH_CONS_DOWN_MASK GENMASK(3, 0)
+#define CPR3_THRESH_CONS_DOWN_SHIFT 0
+#define CPR3_THRESH_CONS_UP_MASK GENMASK(7, 4)
+#define CPR3_THRESH_CONS_UP_SHIFT 4
+#define CPR3_THRESH_DOWN_THRESH_MASK GENMASK(12, 8)
+#define CPR3_THRESH_DOWN_THRESH_SHIFT 8
+#define CPR3_THRESH_UP_THRESH_MASK GENMASK(17, 13)
+#define CPR3_THRESH_UP_THRESH_SHIFT 13
+
+#define CPR3_REG_RO_MASK(thread) (0x80C + 0x440 * (thread))
+
+#define CPR3_REG_RESULT0(thread) (0x810 + 0x440 * (thread))
+#define CPR3_RESULT0_BUSY_MASK BIT(0)
+#define CPR3_RESULT0_STEP_DN_MASK BIT(1)
+#define CPR3_RESULT0_STEP_UP_MASK BIT(2)
+#define CPR3_RESULT0_ERROR_STEPS_MASK GENMASK(7, 3)
+#define CPR3_RESULT0_ERROR_STEPS_SHIFT 3
+#define CPR3_RESULT0_ERROR_MASK GENMASK(19, 8)
+#define CPR3_RESULT0_ERROR_SHIFT 8
+#define CPR3_RESULT0_NEGATIVE_MASK BIT(20)
+
+#define CPR3_REG_RESULT1(thread) (0x814 + 0x440 * (thread))
+#define CPR3_RESULT1_QUOT_MIN_MASK GENMASK(11, 0)
+#define CPR3_RESULT1_QUOT_MIN_SHIFT 0
+#define CPR3_RESULT1_QUOT_MAX_MASK GENMASK(23, 12)
+#define CPR3_RESULT1_QUOT_MAX_SHIFT 12
+#define CPR3_RESULT1_RO_MIN_MASK GENMASK(27, 24)
+#define CPR3_RESULT1_RO_MIN_SHIFT 24
+#define CPR3_RESULT1_RO_MAX_MASK GENMASK(31, 28)
+#define CPR3_RESULT1_RO_MAX_SHIFT 28
+
+#define CPR3_REG_RESULT2(thread) (0x818 + 0x440 * (thread))
+#define CPR3_RESULT2_STEP_QUOT_MIN_MASK GENMASK(5, 0)
+#define CPR3_RESULT2_STEP_QUOT_MIN_SHIFT 0
+#define CPR3_RESULT2_STEP_QUOT_MAX_MASK GENMASK(11, 6)
+#define CPR3_RESULT2_STEP_QUOT_MAX_SHIFT 6
+#define CPR3_RESULT2_SENSOR_MIN_MASK GENMASK(23, 16)
+#define CPR3_RESULT2_SENSOR_MIN_SHIFT 16
+#define CPR3_RESULT2_SENSOR_MAX_MASK GENMASK(31, 24)
+#define CPR3_RESULT2_SENSOR_MAX_SHIFT 24
+
+#define CPR3_REG_IRQ_EN 0x81C
+#define CPR3_REG_IRQ_CLEAR 0x820
+#define CPR3_REG_IRQ_STATUS 0x824
+#define CPR3_IRQ_UP BIT(3)
+#define CPR3_IRQ_MID BIT(2)
+#define CPR3_IRQ_DOWN BIT(1)
+
+#define CPR3_REG_TARGET_QUOT(thread, ro) \
+ (0x840 + 0x440 * (thread) + 0x4 * (ro))
+
+/* Registers found only on controllers that support HW closed-loop. */
+#define CPR3_REG_PD_THROTTLE 0xE8
+#define CPR3_PD_THROTTLE_DISABLE 0x0
+
+#define CPR3_REG_HW_CLOSED_LOOP 0x3000
+#define CPR3_HW_CLOSED_LOOP_ENABLE 0x0
+#define CPR3_HW_CLOSED_LOOP_DISABLE 0x1
+
+#define CPR3_REG_CPR_TIMER_MID_CONT 0x3004
+#define CPR3_REG_CPR_TIMER_UP_DN_CONT 0x3008
+
+#define CPR3_REG_LAST_MEASUREMENT 0x7F8
+#define CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT 0
+#define CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT 4
+#define CPR3_LAST_MEASUREMENT_THREAD_DN(thread) \
+ (BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_DN_SHIFT)
+#define CPR3_LAST_MEASUREMENT_THREAD_UP(thread) \
+ (BIT(thread) << CPR3_LAST_MEASUREMENT_THREAD_UP_SHIFT)
+#define CPR3_LAST_MEASUREMENT_AGGR_DN BIT(8)
+#define CPR3_LAST_MEASUREMENT_AGGR_MID BIT(9)
+#define CPR3_LAST_MEASUREMENT_AGGR_UP BIT(10)
+#define CPR3_LAST_MEASUREMENT_VALID BIT(11)
+#define CPR3_LAST_MEASUREMENT_SAW_ERROR BIT(12)
+#define CPR3_LAST_MEASUREMENT_PD_BYPASS_MASK GENMASK(23, 16)
+#define CPR3_LAST_MEASUREMENT_PD_BYPASS_SHIFT 16
+
+/* CPR4 controller specific registers and bit definitions */
+#define CPR4_REG_CPR_TIMER_CLAMP 0x10
+#define CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN BIT(27)
+
+#define CPR4_REG_MISC 0x700
+#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK GENMASK(23, 20)
+#define CPR4_MISC_MARGIN_TABLE_ROW_SELECT_SHIFT 20
+#define CPR4_MISC_TEMP_SENSOR_ID_START_MASK GENMASK(27, 24)
+#define CPR4_MISC_TEMP_SENSOR_ID_START_SHIFT 24
+#define CPR4_MISC_TEMP_SENSOR_ID_END_MASK GENMASK(31, 28)
+#define CPR4_MISC_TEMP_SENSOR_ID_END_SHIFT 28
+
+#define CPR4_REG_SAW_ERROR_STEP_LIMIT 0x7A4
+#define CPR4_SAW_ERROR_STEP_LIMIT_UP_MASK GENMASK(4, 0)
+#define CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT 0
+#define CPR4_SAW_ERROR_STEP_LIMIT_DN_MASK GENMASK(9, 5)
+#define CPR4_SAW_ERROR_STEP_LIMIT_DN_SHIFT 5
+
+#define CPR4_REG_MARGIN_TEMP_CORE_TIMERS 0x7A8
+#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_MASK GENMASK(28, 18)
+#define CPR4_MARGIN_TEMP_CORE_TIMERS_SETTLE_VOLTAGE_COUNT_SHIFT 18
+
+#define CPR4_REG_MARGIN_TEMP_CORE(core) (0x7AC + 0x4 * (core))
+#define CPR4_MARGIN_TEMP_CORE_ADJ_MASK GENMASK(7, 0)
+#define CPR4_MARGIN_TEMP_CORE_ADJ_SHIFT 8
+
+#define CPR4_REG_MARGIN_TEMP_POINT0N1 0x7F0
+#define CPR4_MARGIN_TEMP_POINT0_MASK GENMASK(11, 0)
+#define CPR4_MARGIN_TEMP_POINT0_SHIFT 0
+#define CPR4_MARGIN_TEMP_POINT1_MASK GENMASK(23, 12)
+#define CPR4_MARGIN_TEMP_POINT1_SHIFT 12
+#define CPR4_REG_MARGIN_TEMP_POINT2 0x7F4
+#define CPR4_MARGIN_TEMP_POINT2_MASK GENMASK(11, 0)
+#define CPR4_MARGIN_TEMP_POINT2_SHIFT 0
+
+#define CPR4_REG_MARGIN_ADJ_CTL 0x7F8
+#define CPR4_MARGIN_ADJ_CTL_BOOST_EN BIT(0)
+#define CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN BIT(1)
+#define CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN BIT(2)
+#define CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN BIT(3)
+#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK BIT(4)
+#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE BIT(4)
+#define CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE 0
+#define CPR4_MARGIN_ADJ_CTL_PER_RO_KV_MARGIN_EN BIT(7)
+#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN BIT(8)
+#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_MASK GENMASK(16, 12)
+#define CPR4_MARGIN_ADJ_CTL_PMIC_STEP_SIZE_SHIFT 12
+#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_MASK GENMASK(21, 19)
+#define CPR4_MARGIN_ADJ_CTL_INITIAL_TEMP_BAND_SHIFT 19
+#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_MASK GENMASK(25, 22)
+#define CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT 22
+#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_MASK GENMASK(31, 26)
+#define CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_STEP_QUOT_SHIFT 26
+
+#define CPR4_REG_CPR_MASK_THREAD(thread) (0x80C + 0x440 * (thread))
+#define CPR4_CPR_MASK_THREAD_DISABLE_THREAD BIT(31)
+#define CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK GENMASK(15, 0)
+
+/* CPRh controller specific registers and bit definitions */
+#define CPRH_REG_CORNER(corner) (0x3A00 + 0x4 * (corner))
+#define CPRH_CORNER_INIT_VOLTAGE_MASK GENMASK(7, 0)
+#define CPRH_CORNER_INIT_VOLTAGE_SHIFT 0
+#define CPRH_CORNER_FLOOR_VOLTAGE_MASK GENMASK(15, 8)
+#define CPRH_CORNER_FLOOR_VOLTAGE_SHIFT 8
+#define CPRH_CORNER_QUOT_DELTA_MASK GENMASK(24, 16)
+#define CPRH_CORNER_QUOT_DELTA_SHIFT 16
+#define CPRH_CORNER_RO_SEL_MASK GENMASK(28, 25)
+#define CPRH_CORNER_RO_SEL_SHIFT 25
+#define CPRH_CORNER_CPR_CL_DISABLE BIT(29)
+#define CPRH_CORNER_CORE_TEMP_MARGIN_DISABLE BIT(30)
+#define CPRH_CORNER_LAST_KNOWN_VOLTAGE_ENABLE BIT(31)
+#define CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE 255
+#define CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE 255
+#define CPRH_CORNER_QUOT_DELTA_MAX_VALUE 511
+
+#define CPRH_REG_CTL 0x3AA0
+#define CPRH_CTL_OSM_ENABLED BIT(0)
+#define CPRH_CTL_BASE_VOLTAGE_MASK GENMASK(10, 1)
+#define CPRH_CTL_BASE_VOLTAGE_SHIFT 1
+#define CPRH_CTL_INIT_MODE_MASK GENMASK(16, 11)
+#define CPRH_CTL_INIT_MODE_SHIFT 11
+#define CPRH_CTL_MODE_SWITCH_DELAY_MASK GENMASK(24, 17)
+#define CPRH_CTL_MODE_SWITCH_DELAY_SHIFT 17
+#define CPRH_CTL_VOLTAGE_MULTIPLIER_MASK GENMASK(28, 25)
+#define CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT 25
+#define CPRH_CTL_LAST_KNOWN_VOLTAGE_MARGIN_MASK GENMASK(31, 29)
+#define CPRH_CTL_LAST_KNOWN_VOLTAGE_MARGIN_SHIFT 29
+
+#define CPRH_REG_STATUS 0x3AA4
+#define CPRH_STATUS_CORNER GENMASK(5, 0)
+#define CPRH_STATUS_CORNER_LAST_VOLT_MASK GENMASK(17, 6)
+#define CPRH_STATUS_CORNER_LAST_VOLT_SHIFT 6
+
+#define CPRH_REG_CORNER_BAND 0x3AA8
+#define CPRH_CORNER_BAND_MASK GENMASK(5, 0)
+#define CPRH_CORNER_BAND_SHIFT 6
+#define CPRH_CORNER_BAND_MAX_COUNT 4
+
+#define CPRH_MARGIN_TEMP_CORE_VBAND(core, vband) \
+ ((vband) == 0 ? CPR4_REG_MARGIN_TEMP_CORE(core) \
+ : 0x3AB0 + 0x40 * ((vband) - 1) + 0x4 * (core))
+
+/*
+ * 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
+
+/*
+ * The number of times the CPRh controller multiplies the mode switch
+ * delay before utilizing it.
+ */
+#define CPRH_MODE_SWITCH_DELAY_FACTOR 4
+
+/*
+ * The number of times the CPRh controller multiplies the delta quotient
+ * steps before utilizing it.
+ */
+#define CPRH_DELTA_QUOT_STEP_FACTOR 4
+
+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_set_base_target_quot() - configure the target quotient
+ * for each RO of the CPR3 regulator for CPRh operation.
+ * In particular, the quotient of the RO selected for operation
+ * should correspond to the lowest target quotient across the
+ * corners supported by the single regulator of the CPR3 thread.
+ * @vreg: Pointer to the CPR3 regulator
+ * @base_quots: Pointer to the base quotient array. The array must be
+ * of size CPR3_RO_COUNT and it is populated with the
+ * base quotient per-RO.
+ *
+ * Return: none
+ */
+static void cpr3_regulator_set_base_target_quot(struct cpr3_regulator *vreg,
+ u32 *base_quots)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ int i, j, ro_mask = CPR3_RO_MASK;
+ u32 min_quot;
+
+ for (i = 0; i < vreg->corner_count; i++)
+ ro_mask &= vreg->corner[i].ro_mask;
+
+ /* Unmask the ROs selected for active use. */
+ cpr3_write(ctrl, CPR3_REG_RO_MASK(vreg->thread->thread_id),
+ ro_mask);
+
+ for (i = 0; i < CPR3_RO_COUNT; i++) {
+ for (j = 0, min_quot = INT_MAX; j < vreg->corner_count; j++)
+ if (vreg->corner[j].target_quot[i])
+ min_quot = min(min_quot,
+ vreg->corner[j].target_quot[i]);
+
+ if (min_quot == INT_MAX)
+ min_quot = 0;
+
+ cpr3_write(ctrl,
+ CPR3_REG_TARGET_QUOT(vreg->thread->thread_id, i),
+ min_quot);
+
+ base_quots[i] = min_quot;
+ }
+}
+
+/**
+ * cpr3_regulator_init_cprh_corners() - configure the per-corner CPRh registers
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function programs the controller registers which contain all information
+ * necessary to resolve the closed-loop voltage per-corner at runtime such as
+ * open-loop and floor voltages, target quotient delta, and RO select value.
+ * These registers also provide a means to disable closed-loop operation, core
+ * and temperature adjustments.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_init_cprh_corners(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr3_corner *corner;
+ u32 reg, delta_quot_steps, ro_sel;
+ u32 *base_quots;
+ int open_loop_volt_steps, floor_volt_steps, i, j, rc = 0;
+
+ base_quots = kcalloc(CPR3_RO_COUNT, sizeof(*base_quots),
+ GFP_KERNEL);
+ if (!base_quots)
+ return -ENOMEM;
+
+ cpr3_regulator_set_base_target_quot(vreg, base_quots);
+
+ for (i = 0; i < vreg->corner_count; i++) {
+ corner = &vreg->corner[i];
+ for (j = 0, ro_sel = INT_MAX; j < CPR3_RO_COUNT; j++) {
+ if (corner->target_quot[j]) {
+ ro_sel = j;
+ break;
+ }
+ }
+
+ if (ro_sel == INT_MAX) {
+ if (!corner->proc_freq) {
+ /*
+ * Corner is not used as active DCVS set point
+ * select RO 0 arbitrarily.
+ */
+ ro_sel = 0;
+ } else {
+ cpr3_err(vreg, "corner=%d has invalid RO select value\n",
+ i);
+ rc = -EINVAL;
+ goto free_base_quots;
+ }
+ }
+
+ open_loop_volt_steps = DIV_ROUND_UP(corner->open_loop_volt -
+ ctrl->base_volt,
+ ctrl->step_volt);
+ floor_volt_steps = DIV_ROUND_UP(corner->floor_volt -
+ ctrl->base_volt,
+ ctrl->step_volt);
+ delta_quot_steps = corner->proc_freq ?
+ DIV_ROUND_UP(corner->target_quot[ro_sel] -
+ base_quots[ro_sel],
+ CPRH_DELTA_QUOT_STEP_FACTOR) :
+ 0;
+
+ if (open_loop_volt_steps > CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE ||
+ floor_volt_steps > CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE ||
+ delta_quot_steps > CPRH_CORNER_QUOT_DELTA_MAX_VALUE) {
+ cpr3_err(ctrl, "invalid CPRh corner configuration: open_loop_volt_steps=%d (%d max.), floor_volt_steps=%d (%d max), delta_quot_steps=%d (%d max)\n",
+ open_loop_volt_steps,
+ CPRH_CORNER_INIT_VOLTAGE_MAX_VALUE,
+ floor_volt_steps,
+ CPRH_CORNER_FLOOR_VOLTAGE_MAX_VALUE,
+ delta_quot_steps,
+ CPRH_CORNER_QUOT_DELTA_MAX_VALUE);
+ rc = -EINVAL;
+ goto free_base_quots;
+ }
+
+ reg = (open_loop_volt_steps << CPRH_CORNER_INIT_VOLTAGE_SHIFT)
+ & CPRH_CORNER_INIT_VOLTAGE_MASK;
+ reg |= (floor_volt_steps << CPRH_CORNER_FLOOR_VOLTAGE_SHIFT)
+ & CPRH_CORNER_FLOOR_VOLTAGE_MASK;
+ reg |= (delta_quot_steps << CPRH_CORNER_QUOT_DELTA_SHIFT)
+ & CPRH_CORNER_QUOT_DELTA_MASK;
+ reg |= (ro_sel << CPRH_CORNER_RO_SEL_SHIFT)
+ & CPRH_CORNER_RO_SEL_MASK;
+
+ if (corner->use_open_loop)
+ reg |= CPRH_CORNER_CPR_CL_DISABLE;
+
+ cpr3_debug(ctrl, "corner=%d open_loop_volt_steps=%d, floor_volt_steps=%d, delta_quot_steps=%d, base_volt=%d, step_volt=%d, base_quot=%d\n",
+ i, open_loop_volt_steps, floor_volt_steps,
+ delta_quot_steps, ctrl->base_volt,
+ ctrl->step_volt, base_quots[ro_sel]);
+ cpr3_write(ctrl, CPRH_REG_CORNER(i), reg);
+ }
+
+free_base_quots:
+ kfree(base_quots);
+ return rc;
+}
+
+/**
+ * cprh_controller_program_sdelta() - programs hardware SDELTA registers with
+ * the margins that need to be applied at different online
+ * core-count and temperature bands for each corner band. Also,
+ * programs hardware register configuration for core-count and
+ * temp-based adjustments
+ *
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * CPR interface/bus clocks must be enabled before calling this function.
+ *
+ * Return: none
+ */
+static void cprh_controller_program_sdelta(
+ struct cpr3_controller *ctrl)
+{
+ struct cpr3_regulator *vreg = &ctrl->thread[0].vreg[0];
+ struct cprh_corner_band *corner_band;
+ struct cpr4_sdelta *sdelta;
+ int i, j, index;
+ u32 reg = 0;
+
+ if (!vreg->allow_core_count_adj && !vreg->allow_temp_adj)
+ return;
+
+ cpr4_regulator_init_temp_points(ctrl);
+
+ for (i = 0; i < CPRH_CORNER_BAND_MAX_COUNT; i++) {
+ reg |= (i < vreg->corner_band_count ?
+ vreg->corner_band[i].corner
+ & CPRH_CORNER_BAND_MASK :
+ vreg->corner_count + 1)
+ << (i * CPRH_CORNER_BAND_SHIFT);
+ }
+
+ cpr3_write(ctrl, CPRH_REG_CORNER_BAND, reg);
+
+ for (i = 0; i < vreg->corner_band_count; i++) {
+ corner_band = &vreg->corner_band[i];
+ sdelta = corner_band->sdelta;
+
+ if (!sdelta->allow_core_count_adj && !sdelta->allow_temp_adj) {
+ /*
+ * Per-online-core and per-temperature margin
+ * adjustments are disabled for this corner band.
+ */
+ continue;
+ }
+
+ if (vreg->allow_core_count_adj)
+ cpr3_write_temp_core_margin(ctrl,
+ CPRH_MARGIN_TEMP_CORE_VBAND(0, i),
+ &sdelta->table[0]);
+
+ for (j = 0; j < sdelta->max_core_count; j++) {
+ index = j * sdelta->temp_band_count;
+
+ cpr3_write_temp_core_margin(ctrl,
+ CPRH_MARGIN_TEMP_CORE_VBAND(
+ sdelta->allow_core_count_adj
+ ? j + 1 : vreg->max_core_count, i),
+ &sdelta->table[index]);
+ }
+ }
+
+ if (!vreg->allow_core_count_adj) {
+ cpr3_masked_write(ctrl, CPR4_REG_MISC,
+ CPR4_MISC_MARGIN_TABLE_ROW_SELECT_MASK,
+ vreg->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_HW_CLOSED_LOOP_EN_MASK,
+ vreg->max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
+ | ((vreg->allow_core_count_adj)
+ ? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
+ | (vreg->allow_temp_adj ? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
+ | ((ctrl->use_hw_closed_loop)
+ ? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
+ | (ctrl->use_hw_closed_loop
+ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE : 0));
+
+ /* Ensure that previous CPR register writes complete */
+ mb();
+}
+
+static int cprh_regulator_aging_adjust(struct cpr3_controller *ctrl);
+
+/**
+ * cpr3_regulator_init_cprh() - performs hardware initialization at the
+ * controller and thread level required for CPRh operation.
+ * @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_regulator_init_cprh(struct cpr3_controller *ctrl)
+{
+ u32 reg, pmic_step_size = 1;
+ u64 temp;
+ int rc;
+
+ /* Single thread, single regulator supported */
+ if (ctrl->thread_count != 1) {
+ cpr3_err(ctrl, "expected 1 thread but found %d\n",
+ ctrl->thread_count);
+ return -EINVAL;
+ } else if (ctrl->thread[0].vreg_count != 1) {
+ cpr3_err(ctrl, "expected 1 regulator but found %d\n",
+ ctrl->thread[0].vreg_count);
+ return -EINVAL;
+ }
+
+ rc = cprh_regulator_aging_adjust(ctrl);
+ if (rc && rc != -ETIMEDOUT) {
+ /*
+ * Don't fail initialization if the CPR aging measurement
+ * timed out due to sensors not being available.
+ */
+ cpr3_err(ctrl, "CPR aging adjustment failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ cprh_controller_program_sdelta(ctrl);
+
+ rc = cpr3_regulator_init_cprh_corners(&ctrl->thread[0].vreg[0]);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize CPRh corner registers\n");
+ return rc;
+ }
+
+ 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));
+
+ 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));
+
+ 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);
+ }
+
+ if (ctrl->corner_switch_delay_time) {
+ /*
+ * Configure the settling timer used to delay
+ * following SAW requests
+ */
+ temp = (u64)ctrl->cpr_clock_rate
+ * (u64)ctrl->corner_switch_delay_time;
+ do_div(temp, 1000000000);
+ do_div(temp, CPRH_MODE_SWITCH_DELAY_FACTOR);
+ cpr3_masked_write(ctrl, CPRH_REG_CTL,
+ CPRH_CTL_MODE_SWITCH_DELAY_MASK,
+ temp << CPRH_CTL_MODE_SWITCH_DELAY_SHIFT);
+ }
+
+ /*
+ * Program base voltage and voltage multiplier values which
+ * are used for floor and initial voltage calculations by the
+ * CPRh controller.
+ */
+ reg = (DIV_ROUND_UP(ctrl->base_volt, ctrl->step_volt)
+ << CPRH_CTL_BASE_VOLTAGE_SHIFT)
+ & CPRH_CTL_BASE_VOLTAGE_MASK;
+ reg |= (DIV_ROUND_UP(ctrl->step_volt, 1000)
+ << CPRH_CTL_VOLTAGE_MULTIPLIER_SHIFT)
+ & CPRH_CTL_VOLTAGE_MULTIPLIER_MASK;
+ /* Enable OSM block interface with CPR */
+ reg |= CPRH_CTL_OSM_ENABLED;
+ cpr3_masked_write(ctrl, CPRH_REG_CTL, CPRH_CTL_BASE_VOLTAGE_MASK
+ | CPRH_CTL_VOLTAGE_MULTIPLIER_MASK
+ | CPRH_CTL_OSM_ENABLED, reg);
+
+ /* Enable loop_en */
+ cpr3_ctrl_loop_enable(ctrl);
+
+ 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 ||
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ 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->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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_CPR3) {
+ rc = msm_spm_avs_enable_irq(0,
+ MSM_SPM_AVS_IRQ_MAX);
+ if (rc) {
+ cpr3_err(ctrl, "could not enable max IRQ, 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;
+ }
+ } else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ rc = cpr3_regulator_init_cprh(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "CPRh-specific controller initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* Ensure that all register writes complete before disabling clocks. */
+ wmb();
+
+ /* Keep CPR clocks on for CPRh full HW closed-loop operation */
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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 &&
+ ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH)
+ mode = ctrl->use_hw_closed_loop
+ ? "HW closed-loop" : "SW closed-loop";
+ else if (ctrl->supports_hw_closed_loop &&
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
+ mode = ctrl->use_hw_closed_loop
+ ? "full HW closed-loop" : "open-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;
+}
+
+/**
+ * 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;
+
+ 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_config_ldo_retention() - configure per-regulator LDO retention
+ * mode
+ * @vreg: Pointer to the CPR3 regulator to configure
+ * @ref_volt: Reference voltage used to determine if LDO retention
+ * mode can be allowed. It corresponds either to the
+ * aggregated floor voltage or the next VDD supply setpoint
+ *
+ * This function determines if a CPR3 regulator's configuration satisfies safe
+ * operating voltages for LDO retention and uses the regulator_allow_bypass()
+ * interface on the LDO retention regulator to enable or disable such feature
+ * accordingly.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_config_ldo_retention(struct cpr3_regulator *vreg,
+ int ref_volt)
+{
+ struct regulator *ldo_ret_reg = vreg->ldo_ret_regulator;
+ int retention_volt, rc;
+ enum msm_ldo_supply_mode mode;
+
+ if (!ldo_ret_reg) {
+ /* LDO retention regulator is not defined */
+ return 0;
+ }
+
+ retention_volt = regulator_get_voltage(ldo_ret_reg);
+ if (retention_volt < 0) {
+ cpr3_err(vreg, "regulator_get_voltage(ldo_ret) failed, rc=%d\n",
+ retention_volt);
+ return retention_volt;
+
+ }
+
+ mode = ref_volt >= retention_volt + vreg->ldo_min_headroom_volt
+ ? LDO_MODE : BHS_MODE;
+
+ rc = regulator_allow_bypass(ldo_ret_reg, mode);
+ if (rc)
+ cpr3_err(vreg, "regulator_allow_bypass(ldo_ret) == %s failed, rc=%d\n",
+ mode ? "true" : "false", rc);
+
+ return rc;
+}
+
+/**
+ * cpr3_regulator_config_kryo_ldo_mem_acc() - configure the mem-acc regulator
+ * corner based upon a future Kryo LDO regulator voltage setpoint
+ * @vreg: Pointer to the CPR3 regulator
+ * @new_volt: New voltage in microvolts that the LDO regulator needs
+ * to end up at
+ *
+ * This function determines if a new LDO regulator set point will result
+ * in crossing the voltage threshold that requires reconfiguration of
+ * the mem-acc regulator associated with a CPR3 regulator and if so, performs
+ * the correct sequence to select the correct mem-acc corner.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_config_kryo_ldo_mem_acc(struct cpr3_regulator *vreg,
+ int new_volt)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct regulator *ldo_reg = vreg->ldo_regulator;
+ struct regulator *mem_acc_reg = vreg->mem_acc_regulator;
+ int mem_acc_volt = ctrl->mem_acc_threshold_volt;
+ int last_volt, safe_volt, mem_acc_corn, rc;
+ enum msm_apm_supply apm_mode;
+
+ if (!mem_acc_reg || !mem_acc_volt || !ldo_reg)
+ return 0;
+
+ apm_mode = msm_apm_get_supply(ctrl->apm);
+ if (apm_mode < 0) {
+ cpr3_err(ctrl, "APM get supply failed, rc=%d\n",
+ apm_mode);
+ return apm_mode;
+ }
+
+ last_volt = regulator_get_voltage(ldo_reg);
+ if (last_volt < 0) {
+ cpr3_err(vreg, "regulator_get_voltage(ldo) failed, rc=%d\n",
+ last_volt);
+ return last_volt;
+ }
+
+ if (((last_volt < mem_acc_volt && mem_acc_volt <= new_volt)
+ || (last_volt >= mem_acc_volt && mem_acc_volt > new_volt))) {
+
+ if (apm_mode == ctrl->apm_high_supply)
+ safe_volt = min(vreg->ldo_max_volt, mem_acc_volt);
+ else
+ safe_volt = min(max(ctrl->system_supply_max_volt -
+ vreg->ldo_max_headroom_volt,
+ mem_acc_volt), vreg->ldo_max_volt);
+
+ rc = regulator_set_voltage(ldo_reg, safe_volt,
+ max(new_volt, last_volt));
+ if (rc) {
+ cpr3_err(ctrl, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
+ mem_acc_volt, rc);
+ return rc;
+ }
+
+ mem_acc_corn = new_volt < mem_acc_volt ?
+ ctrl->mem_acc_corner_map[CPR3_MEM_ACC_LOW_CORNER] :
+ ctrl->mem_acc_corner_map[CPR3_MEM_ACC_HIGH_CORNER];
+
+ rc = regulator_set_voltage(mem_acc_reg, mem_acc_corn,
+ mem_acc_corn);
+ if (rc) {
+ cpr3_err(ctrl, "regulator_set_voltage(mem_acc) == %d failed, rc=%d\n",
+ 0, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * cpr3_regulator_kryo_bhs_prepare() - configure the Kryo LDO regulator
+ * associated with a CPR3 regulator in preparation for BHS
+ * mode switch.
+ * @vreg: Pointer to the CPR3 regulator
+ * @vdd_volt: Last known settled voltage in microvolts for the VDD
+ * supply
+ * @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
+ * the VDD supply
+ *
+ * This function performs the necessary steps prior to switching a Kryo LDO
+ * regulator to BHS mode (LDO bypassed mode).
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_kryo_bhs_prepare(struct cpr3_regulator *vreg,
+ int vdd_volt, int vdd_ceiling_volt)
+{
+ struct regulator *ldo_reg = vreg->ldo_regulator;
+ int bhs_volt, rc;
+
+ bhs_volt = vdd_volt - vreg->ldo_min_headroom_volt;
+ if (bhs_volt > vreg->ldo_max_volt) {
+ cpr3_debug(vreg, "limited to LDO output of %d uV when switching to BHS mode\n",
+ vreg->ldo_max_volt);
+ bhs_volt = vreg->ldo_max_volt;
+ }
+
+ rc = cpr3_regulator_config_kryo_ldo_mem_acc(vreg, bhs_volt);
+ if (rc) {
+ cpr3_err(vreg, "failed to configure mem-acc settings\n");
+ return rc;
+ }
+
+ rc = regulator_set_voltage(ldo_reg, bhs_volt, min(vdd_ceiling_volt,
+ vreg->ldo_max_volt));
+ if (rc) {
+ cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
+ bhs_volt, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+/**
+ * cpr3_regulator_set_bhs_mode() - configure the LDO regulator associated with
+ * a CPR3 regulator to BHS mode
+ * @vreg: Pointer to the CPR3 regulator
+ * @vdd_volt: Last known settled voltage in microvolts for the VDD
+ * supply
+ * @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
+ * the VDD supply
+ *
+ * This function performs the necessary steps to switch an LDO regulator
+ * to BHS mode (LDO bypassed mode).
+ */
+static int cpr3_regulator_set_bhs_mode(struct cpr3_regulator *vreg,
+ int vdd_volt, int vdd_ceiling_volt)
+{
+ struct regulator *ldo_reg = vreg->ldo_regulator;
+ int rc;
+
+ if (vreg->ldo_type == CPR3_LDO_KRYO) {
+ rc = cpr3_regulator_kryo_bhs_prepare(vreg, vdd_volt,
+ vdd_ceiling_volt);
+ if (rc) {
+ cpr3_err(vreg, "cpr3 regulator bhs mode prepare failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = regulator_allow_bypass(ldo_reg, BHS_MODE);
+ if (rc) {
+ cpr3_err(vreg, "regulator_allow_bypass(bhs) == %s failed, rc=%d\n",
+ BHS_MODE ? "true" : "false", rc);
+ return rc;
+ }
+ vreg->ldo_regulator_bypass = BHS_MODE;
+
+ return rc;
+}
+
+/**
+ * cpr3_regulator_ldo_apm_prepare() - configure LDO regulators associated
+ * with each CPR3 regulator of a CPR3 controller in preparation
+ * for an APM switch.
+ * @ctrl: Pointer to the CPR3 controller
+ * @new_volt: New voltage in microvolts that the 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 ensures LDO regulator hardware requirements are met before
+ * an APM switch is requested. The function must be called as the last step
+ * before switching the APM mode.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_ldo_apm_prepare(struct cpr3_controller *ctrl,
+ int new_volt, int last_volt,
+ struct cpr3_corner *aggr_corner)
+{
+ struct cpr3_regulator *vreg;
+ struct cpr3_corner *current_corner;
+ enum msm_apm_supply apm_mode;
+ int i, j, safe_volt, max_volt, ldo_volt, ref_volt, rc;
+
+ apm_mode = msm_apm_get_supply(ctrl->apm);
+ if (apm_mode < 0) {
+ cpr3_err(ctrl, "APM get supply failed, rc=%d\n", apm_mode);
+ return apm_mode;
+ }
+
+ if (apm_mode == ctrl->apm_low_supply ||
+ new_volt >= ctrl->apm_threshold_volt)
+ return 0;
+
+ /*
+ * Guarantee LDO maximum headroom is not violated when the APM is
+ * switched to the system-supply source.
+ */
+ 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->vreg_enabled || vreg->current_corner
+ == CPR3_REGULATOR_CORNER_INVALID)
+ continue;
+
+ if (!vreg->ldo_regulator || !vreg->ldo_mode_allowed ||
+ vreg->ldo_regulator_bypass == BHS_MODE)
+ continue;
+
+ /*
+ * If the new VDD configuration does not satisfy
+ * requirements for LDO usage, switch the regulator
+ * to BHS mode. By doing so, the LDO maximum headroom
+ * does not need to be enforced.
+ */
+ current_corner = &vreg->corner[vreg->current_corner];
+ ldo_volt = current_corner->open_loop_volt
+ - vreg->ldo_adjust_volt;
+ ref_volt = ctrl->use_hw_closed_loop ?
+ aggr_corner->floor_volt :
+ new_volt;
+
+ if (ref_volt < ldo_volt + vreg->ldo_min_headroom_volt
+ || ldo_volt < ctrl->system_supply_max_volt -
+ vreg->ldo_max_headroom_volt ||
+ ldo_volt > vreg->ldo_max_volt) {
+ rc = cpr3_regulator_set_bhs_mode(vreg,
+ last_volt, aggr_corner->ceiling_volt);
+ if (rc)
+ return rc;
+ /*
+ * Do not enforce LDO maximum headroom since the
+ * regulator is now configured to BHS mode.
+ */
+ continue;
+ }
+
+ safe_volt = min(max(ldo_volt,
+ ctrl->system_supply_max_volt
+ - vreg->ldo_max_headroom_volt),
+ vreg->ldo_max_volt);
+ max_volt = min(ctrl->system_supply_max_volt,
+ vreg->ldo_max_volt);
+
+ rc = regulator_set_voltage(vreg->ldo_regulator,
+ safe_volt, max_volt);
+ if (rc) {
+ cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
+ safe_volt, rc);
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * cpr3_regulator_config_vreg_kryo_ldo() - configure the voltage and bypass
+ * state for the Kryo LDO regulator associated with a single CPR3
+ * regulator.
+ *
+ * @vreg: Pointer to the CPR3 regulator
+ * @vdd_floor_volt: Last known aggregated floor voltage in microvolts for
+ * the VDD supply
+ * @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
+ * the VDD supply
+ * @ref_volt: Reference voltage in microvolts corresponds either to
+ * the aggregated floor voltage or the next VDD supply
+ * setpoint.
+ * @last_volt: Last known voltage in microvolts for the VDD supply
+ *
+ * This function performs all relevant LDO or BHS configurations if a Kryo LDO
+ * regulator is specified.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_config_vreg_kryo_ldo(struct cpr3_regulator *vreg,
+ int vdd_floor_volt, int vdd_ceiling_volt,
+ int ref_volt, int last_volt)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct regulator *ldo_reg = vreg->ldo_regulator;
+ struct cpr3_corner *current_corner;
+ enum msm_apm_supply apm_mode;
+ int rc, ldo_volt, final_ldo_volt, bhs_volt, max_volt, safe_volt;
+
+ current_corner = &vreg->corner[vreg->current_corner];
+ ldo_volt = current_corner->open_loop_volt
+ - vreg->ldo_adjust_volt;
+ bhs_volt = last_volt - vreg->ldo_min_headroom_volt;
+ max_volt = min(vdd_ceiling_volt, vreg->ldo_max_volt);
+
+ if (ref_volt >= ldo_volt + vreg->ldo_min_headroom_volt &&
+ ldo_volt >= ctrl->system_supply_max_volt -
+ vreg->ldo_max_headroom_volt &&
+ bhs_volt >= ctrl->system_supply_max_volt -
+ vreg->ldo_max_headroom_volt &&
+ ldo_volt <= vreg->ldo_max_volt) {
+ /* LDO minimum and maximum headrooms satisfied */
+ apm_mode = msm_apm_get_supply(ctrl->apm);
+ if (apm_mode < 0) {
+ cpr3_err(ctrl, "APM get supply failed, rc=%d\n",
+ apm_mode);
+ return apm_mode;
+ }
+
+ if (vreg->ldo_regulator_bypass == BHS_MODE) {
+ /*
+ * BHS to LDO transition. Configure LDO output
+ * to min(max LDO output, VDD - LDO headroom)
+ * voltage if APM is on high supply source or
+ * min(max(system-supply ceiling - LDO max headroom,
+ * VDD - LDO headroom), max LDO output) if
+ * APM is on low supply source, then switch
+ * regulator mode.
+ */
+ if (apm_mode == ctrl->apm_high_supply)
+ safe_volt = min(vreg->ldo_max_volt, bhs_volt);
+ else
+ safe_volt =
+ min(max(ctrl->system_supply_max_volt -
+ vreg->ldo_max_headroom_volt,
+ bhs_volt),
+ vreg->ldo_max_volt);
+
+ rc = cpr3_regulator_config_kryo_ldo_mem_acc(vreg,
+ safe_volt);
+ if (rc) {
+ cpr3_err(vreg, "failed to configure mem-acc settings\n");
+ return rc;
+ }
+
+ rc = regulator_set_voltage(ldo_reg, safe_volt,
+ max_volt);
+ if (rc) {
+ cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
+ safe_volt, rc);
+ return rc;
+ }
+
+ rc = regulator_allow_bypass(ldo_reg, LDO_MODE);
+ if (rc) {
+ cpr3_err(vreg, "regulator_allow_bypass(ldo) == %s failed, rc=%d\n",
+ LDO_MODE ? "true" : "false", rc);
+ return rc;
+ }
+ vreg->ldo_regulator_bypass = LDO_MODE;
+ }
+
+ /* Configure final LDO output voltage */
+ if (apm_mode == ctrl->apm_high_supply)
+ final_ldo_volt = max(ldo_volt,
+ vdd_ceiling_volt -
+ vreg->ldo_max_headroom_volt);
+ else
+ final_ldo_volt = ldo_volt;
+
+ rc = cpr3_regulator_config_kryo_ldo_mem_acc(vreg,
+ final_ldo_volt);
+ if (rc) {
+ cpr3_err(vreg, "failed to configure mem-acc settings\n");
+ return rc;
+ }
+
+ rc = regulator_set_voltage(ldo_reg, final_ldo_volt, max_volt);
+ if (rc) {
+ cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
+ final_ldo_volt, rc);
+ return rc;
+ }
+ } else {
+ if (vreg->ldo_regulator_bypass == LDO_MODE) {
+ /* LDO to BHS transition */
+ rc = cpr3_regulator_set_bhs_mode(vreg, last_volt,
+ vdd_ceiling_volt);
+ if (rc)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * cpr3_regulator_config_vreg_ldo300() - configure the voltage and bypass state
+ * for the LDO300 regulator associated with a single CPR3
+ * regulator.
+ *
+ * @vreg: Pointer to the CPR3 regulator
+ * @new_volt: New voltage in microvolts that VDD supply needs to
+ * end up at
+ * @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
+ * the VDD supply
+ *
+ * This function performs all relevant LDO or BHS configurations for an LDO300
+ * type regulator.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_config_vreg_ldo300(struct cpr3_regulator *vreg,
+ int new_volt, int vdd_ceiling_volt)
+{
+ struct regulator *ldo_reg = vreg->ldo_regulator;
+ struct cpr3_corner *corner;
+ bool mode;
+ int rc = 0;
+
+ corner = &vreg->corner[vreg->current_corner];
+ mode = corner->ldo_mode_allowed ? LDO_MODE : BHS_MODE;
+
+ if (mode == LDO_MODE) {
+ rc = regulator_set_voltage(ldo_reg, new_volt, vdd_ceiling_volt);
+ if (rc) {
+ cpr3_err(vreg, "regulator_set_voltage(ldo) == %d failed, rc=%d\n",
+ new_volt, rc);
+ return rc;
+ }
+ }
+
+ if (vreg->ldo_regulator_bypass != mode) {
+ rc = regulator_allow_bypass(ldo_reg, mode);
+ if (rc) {
+ cpr3_err(vreg, "regulator_allow_bypass(%s) is failed, rc=%d\n",
+ mode == LDO_MODE ? "ldo" : "bhs", rc);
+ return rc;
+ }
+ vreg->ldo_regulator_bypass = mode;
+ }
+
+ return rc;
+}
+
+/**
+ * cpr3_regulator_config_vreg_ldo() - configure the voltage and bypass state for
+ * the LDO regulator associated with a single CPR3 regulator.
+ *
+ * @vreg: Pointer to the CPR3 regulator
+ * @vdd_floor_volt: Last known aggregated floor voltage in microvolts for
+ * the VDD supply
+ * @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
+ * the VDD supply
+ * @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
+ *
+ * This function identifies the type of LDO regulator associated with a CPR3
+ * regulator and invokes the LDO specific configuration functions.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_config_vreg_ldo(struct cpr3_regulator *vreg,
+ int vdd_floor_volt, int vdd_ceiling_volt,
+ int new_volt, int last_volt)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ int ref_volt, rc;
+
+ ref_volt = ctrl->use_hw_closed_loop ? vdd_floor_volt :
+ new_volt;
+
+ rc = cpr3_regulator_config_ldo_retention(vreg, ref_volt);
+ if (rc)
+ return rc;
+
+ if (!vreg->vreg_enabled ||
+ vreg->current_corner == CPR3_REGULATOR_CORNER_INVALID)
+ return 0;
+
+ switch (vreg->ldo_type) {
+ case CPR3_LDO_KRYO:
+ rc = cpr3_regulator_config_vreg_kryo_ldo(vreg, vdd_floor_volt,
+ vdd_ceiling_volt, ref_volt, last_volt);
+ if (rc)
+ cpr3_err(vreg, "kryo ldo regulator config failed, rc=%d\n",
+ rc);
+ break;
+ case CPR3_LDO300:
+ rc = cpr3_regulator_config_vreg_ldo300(vreg, new_volt,
+ vdd_ceiling_volt);
+ if (rc)
+ cpr3_err(vreg, "ldo300 regulator config failed, rc=%d\n",
+ rc);
+ break;
+ default:
+ cpr3_err(vreg, "invalid ldo regulator type = %d\n",
+ vreg->ldo_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/**
+ * cpr3_regulator_config_ldo() - configure the voltage and bypass state for the
+ * LDO regulator associated with each CPR3 regulator of a CPR3
+ * controller
+ * @ctrl: Pointer to the CPR3 controller
+ * @vdd_floor_volt: Last known aggregated floor voltage in microvolts for
+ * the VDD supply
+ * @vdd_ceiling_volt: Last known aggregated ceiling voltage in microvolts for
+ * the VDD supply
+ * @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
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_regulator_config_ldo(struct cpr3_controller *ctrl,
+ int vdd_floor_volt, int vdd_ceiling_volt,
+ int new_volt, int last_volt)
+{
+ struct cpr3_regulator *vreg;
+ int i, j, 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->ldo_regulator || !vreg->ldo_mode_allowed)
+ continue;
+
+ rc = cpr3_regulator_config_vreg_ldo(vreg,
+ vdd_floor_volt, vdd_ceiling_volt,
+ new_volt, last_volt);
+ if (rc)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * 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 &&
+ (!vreg->ldo_regulator ||
+ vreg->ldo_regulator_bypass
+ == BHS_MODE))
+ 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 ||
+ (vreg->ldo_regulator &&
+ vreg->ldo_regulator_bypass
+ == LDO_MODE))
+ 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 = cpr3_regulator_ldo_apm_prepare(ctrl, new_volt, *last_volt,
+ aggr_corner);
+ if (rc) {
+ cpr3_err(ctrl, "unable to prepare LDO state for APM switch, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ 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) {
+ if (ctrl->support_ldo300_vreg) {
+ rc = cpr3_regulator_config_mem_acc(ctrl, aggr_corner);
+ if (rc)
+ return rc;
+ }
+
+ /* Decreasing VDD voltage */
+ rc = cpr3_regulator_config_ldo(ctrl, aggr_corner->floor_volt,
+ ctrl->aggr_corner.ceiling_volt,
+ new_volt, last_volt);
+ if (rc) {
+ cpr3_err(ctrl, "unable to configure LDO state, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (!ctrl->support_ldo300_vreg) {
+ 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) {
+ /* Increasing VDD voltage */
+ rc = cpr3_regulator_config_ldo(ctrl, aggr_corner->floor_volt,
+ aggr_corner->ceiling_volt,
+ new_volt, new_volt);
+ if (rc) {
+ cpr3_err(ctrl, "unable to configure LDO state, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ 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;
+ aggr_corner->ldo_mode_allowed |= corner->ldo_mode_allowed;
+
+ 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;
+
+ 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);
+ } 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 ||
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ 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 ||
+ ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ 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);
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
+ cprh_adjust_voltages_for_apm(vreg);
+ }
+ }
+
+ 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;
+}
+
+/**
+ * cprh_regulator_aging_adjust() - adjust the target quotients and open-loop
+ * voltages for CPRh regulators based on the output of CPR aging
+ * sensors
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_regulator_aging_adjust(struct cpr3_controller *ctrl)
+{
+ int i, j, id, rc, rc2, aging_volt, init_volt;
+ int max_aging_volt = 0;
+ u32 reg;
+
+ if (!ctrl->aging_required || !ctrl->cpr_enabled)
+ return 0;
+
+ if (!ctrl->vdd_regulator) {
+ cpr3_err(ctrl, "vdd-supply regulator missing\n");
+ return -ENODEV;
+ }
+
+ init_volt = regulator_get_voltage(ctrl->vdd_regulator);
+ if (init_volt < 0) {
+ cpr3_err(ctrl, "could not get vdd-supply voltage, rc=%d\n",
+ init_volt);
+ return init_volt;
+ }
+
+ if (init_volt > ctrl->aging_ref_volt) {
+ cpr3_info(ctrl, "unable to perform CPR aging measurement as vdd=%d uV > aging voltage=%d uV\n",
+ init_volt, ctrl->aging_ref_volt);
+ 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)) {
+ cpr3_info(ctrl, "unable to perform CPR aging measurement as CPR sensor %d is masked\n",
+ id);
+ return 0;
+ }
+ }
+
+ rc = regulator_set_voltage(ctrl->vdd_regulator, ctrl->aging_ref_volt,
+ INT_MAX);
+ if (rc) {
+ cpr3_err(ctrl, "unable to set vdd-supply to aging voltage=%d uV, rc=%d\n",
+ ctrl->aging_ref_volt, rc);
+ return rc;
+ }
+
+ 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:
+ /* 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;
+ }
+
+ rc2 = regulator_set_voltage(ctrl->vdd_regulator, init_volt, INT_MAX);
+ if (rc2) {
+ cpr3_err(ctrl, "unable to reset vdd-supply to initial voltage=%d uV, rc=%d\n",
+ init_volt, rc2);
+ return rc2;
+ }
+
+ if (ctrl->aging_complete_vdd_mode) {
+ rc2 = regulator_set_mode(ctrl->vdd_regulator,
+ ctrl->aging_complete_vdd_mode);
+ if (rc2) {
+ cpr3_err(ctrl, "unable to configure vdd-supply for mode=%u, rc=%d\n",
+ ctrl->aging_complete_vdd_mode, rc2);
+ return rc2;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * 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 int *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;
+
+ 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;
+ }
+
+ cpr3_debug(vreg, "set corner=%d\n", corner);
+done:
+ 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 int selector)
+{
+ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
+
+ if (selector < vreg->corner_count)
+ return selector + CPR3_CORNER_OFFSET;
+ else
+ return 0;
+}
+
+/**
+ * cpr3_regulator_list_corner_voltage() - return the ceiling voltage mapped to
+ * the specified voltage corner
+ * @rdev: Regulator device pointer for the cpr3-regulator
+ * @corner: Voltage corner
+ *
+ * This function is passed as a callback function into the regulator ops that
+ * are registered for each cpr3-regulator device.
+ *
+ * Return: voltage value in microvolts or -EINVAL if the corner is out of range
+ */
+static int cpr3_regulator_list_corner_voltage(struct regulator_dev *rdev,
+ int corner)
+{
+ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
+
+ corner -= CPR3_CORNER_OFFSET;
+
+ if (corner >= 0 && corner < vreg->corner_count)
+ return vreg->corner[corner].ceiling_volt;
+ else
+ return -EINVAL;
+}
+
+/**
+ * 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;
+ }
+
+ if (vreg->ldo_regulator) {
+ rc = regulator_enable(vreg->ldo_regulator);
+ if (rc) {
+ cpr3_err(vreg, "regulator_enable(ldo) 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);
+
+ if (vreg->ldo_regulator && vreg->ldo_regulator_bypass == LDO_MODE) {
+ rc = regulator_get_voltage(ctrl->vdd_regulator);
+ if (rc < 0) {
+ cpr3_err(vreg, "regulator_get_voltage(vdd) failed, rc=%d\n",
+ rc);
+ goto done;
+ }
+
+ /* Switch back to BHS for safe operation */
+ rc = cpr3_regulator_set_bhs_mode(vreg, rc,
+ ctrl->aggr_corner.ceiling_volt);
+ if (rc) {
+ cpr3_err(vreg, "unable to switch to BHS mode, rc=%d\n",
+ rc);
+ goto done;
+ }
+ }
+
+ if (vreg->ldo_regulator) {
+ rc = regulator_disable(vreg->ldo_regulator);
+ if (rc) {
+ cpr3_err(vreg, "regulator_disable(ldo) failed, rc=%d\n",
+ rc);
+ goto done;
+ }
+ }
+ 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;
+ }
+ if (ctrl->support_ldo300_vreg) {
+ rc = regulator_set_voltage(ctrl->system_regulator, 0,
+ INT_MAX);
+ if (rc)
+ cpr3_err(ctrl, "failed to set voltage on system 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,
+ .list_corner_voltage = cpr3_regulator_list_corner_voltage,
+};
+
+/**
+ * cprh_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 of a CPRh controller. The
+ * corner is read directly from CPRh hardware register.
+ *
+ * Return: voltage corner value offset by CPR3_CORNER_OFFSET
+ */
+static int cprh_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct cpr3_regulator *vreg = rdev_get_drvdata(rdev);
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ bool cpr_enabled;
+ u32 reg, rc;
+
+ mutex_lock(&ctrl->lock);
+
+ cpr_enabled = ctrl->cpr_enabled;
+ if (!cpr_enabled) {
+ rc = cpr3_clock_enable(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "clock enable failed, rc=%d\n", rc);
+ mutex_unlock(&ctrl->lock);
+ return CPR3_REGULATOR_CORNER_INVALID;
+ }
+ ctrl->cpr_enabled = true;
+ }
+
+ reg = cpr3_read(vreg->thread->ctrl, CPRH_REG_STATUS);
+
+ if (!cpr_enabled) {
+ cpr3_clock_disable(ctrl);
+ ctrl->cpr_enabled = false;
+ }
+
+ mutex_unlock(&ctrl->lock);
+
+ return (reg & CPRH_STATUS_CORNER)
+ + CPR3_CORNER_OFFSET;
+}
+
+static struct regulator_ops cprh_regulator_ops = {
+ .get_voltage = cprh_regulator_get_voltage,
+ .list_corner_voltage = cpr3_regulator_list_corner_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 rc, 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:
+ rc = msm_spm_avs_clear_irq(0, MSM_SPM_AVS_IRQ_MAX);
+ if (rc)
+ cpr3_err(ctrl, "could not clear max IRQ, rc=%d\n", rc);
+
+ 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;
+ if (vreg->thread->ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ /* CPRh regulators are treated as always-on regulators */
+ rdesc->ops = &cprh_regulator_ops;
+ } else {
+ 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 & 0222))
+ return debugfs_create_file(name, mode, parent, value,
+ &fops_int_ro);
+ /* if there are no read bits set, make write only */
+ if (!(mode & 0444))
+ 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");
+
+/**
+ * cpr3_debug_ldo_mode_allowed_set() - debugfs callback used to change the
+ * value of the CPR3 regulator ldo_mode_allowed flag
+ * @data: Pointer to private data which is equal to the CPR3
+ * regulator pointer
+ * @val: New value for ldo_mode_allowed
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_debug_ldo_mode_allowed_set(void *data, u64 val)
+{
+ struct cpr3_regulator *vreg = data;
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ bool allow = !!val;
+ int rc, vdd_volt;
+
+ mutex_lock(&ctrl->lock);
+
+ if (vreg->ldo_mode_allowed == allow)
+ goto done;
+
+ vreg->ldo_mode_allowed = allow;
+
+ if (!allow && vreg->ldo_regulator_bypass == LDO_MODE) {
+ vdd_volt = regulator_get_voltage(ctrl->vdd_regulator);
+ if (vdd_volt < 0) {
+ cpr3_err(vreg, "regulator_get_voltage(vdd) failed, rc=%d\n",
+ vdd_volt);
+ goto done;
+ }
+
+ /* Switch back to BHS */
+ rc = cpr3_regulator_set_bhs_mode(vreg, vdd_volt,
+ ctrl->aggr_corner.ceiling_volt);
+ if (rc) {
+ cpr3_err(vreg, "unable to switch to BHS mode, rc=%d\n",
+ rc);
+ goto done;
+ }
+ } else {
+ rc = cpr3_regulator_update_ctrl_state(ctrl);
+ if (rc) {
+ cpr3_err(vreg, "could not change LDO mode=%s, rc=%d\n",
+ allow ? "allowed" : "disallowed", rc);
+ goto done;
+ }
+ }
+
+ cpr3_debug(vreg, "LDO mode=%s\n", allow ? "allowed" : "disallowed");
+
+done:
+ mutex_unlock(&ctrl->lock);
+ return 0;
+}
+
+/**
+ * cpr3_debug_ldo_mode_allowed_get() - debugfs callback used to retrieve the
+ * value of the CPR3 regulator ldo_mode_allowed flag
+ * @data: Pointer to private data which is equal to the CPR3
+ * regulator pointer
+ * @val: Output parameter written with a value of the
+ * ldo_mode_allowed flag
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_debug_ldo_mode_allowed_get(void *data, u64 *val)
+{
+ struct cpr3_regulator *vreg = data;
+
+ *val = vreg->ldo_mode_allowed;
+
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_ldo_mode_allowed_fops,
+ cpr3_debug_ldo_mode_allowed_get,
+ cpr3_debug_ldo_mode_allowed_set,
+ "%llu\n");
+
+/**
+ * cpr3_debug_ldo_mode_get() - debugfs callback used to retrieve the state of
+ * the CPR3 regulator's LDO
+ * @data: Pointer to private data which is equal to the CPR3
+ * regulator pointer
+ * @val: Output parameter written with a value of 1 if using
+ * LDO mode or 0 if the LDO is bypassed
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr3_debug_ldo_mode_get(void *data, u64 *val)
+{
+ struct cpr3_regulator *vreg = data;
+
+ *val = (vreg->ldo_regulator_bypass == LDO_MODE);
+
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(cpr3_debug_ldo_mode_fops, cpr3_debug_ldo_mode_get,
+ NULL, "%llu\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", 0444,
+ 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", 0444,
+ 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", 0444,
+ 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", 0444,
+ 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", 0444, 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", 0444, 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", 0444, 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", 0444, vreg_dir,
+ &vreg->fuse_combo);
+ if (IS_ERR_OR_NULL(temp)) {
+ cpr3_err(vreg, "fuse_combo debugfs file creation failed\n");
+ return;
+ }
+
+ if (vreg->ldo_regulator) {
+ temp = debugfs_create_file("ldo_mode", 0444, vreg_dir, vreg,
+ &cpr3_debug_ldo_mode_fops);
+ if (IS_ERR_OR_NULL(temp)) {
+ cpr3_err(vreg, "ldo_mode debugfs file creation failed\n");
+ return;
+ }
+
+ temp = debugfs_create_file("ldo_mode_allowed",
+ 0644, vreg_dir, vreg,
+ &cpr3_debug_ldo_mode_allowed_fops);
+ if (IS_ERR_OR_NULL(temp)) {
+ cpr3_err(vreg, "ldo_mode_allowed debugfs file creation failed\n");
+ return;
+ }
+ }
+
+ temp = debugfs_create_int("corner_count", 0444, 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", 0644, 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", 0444, 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", 0444, 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", 0444, 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", 0444, 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", 0444, 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", 0444, 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;
+
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
+ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
+ ctrl->cpr_allowed_sw && ctrl->use_hw_closed_loop
+ ? CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_ENABLE
+ : CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_DISABLE);
+ } else {
+ 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;
+ }
+ }
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ cpr3_debug(ctrl, "closed-loop=%s\n", enable ?
+ "enabled" : "disabled");
+ } else {
+ cpr3_debug(ctrl, "closed-loop=%s\n", enable &&
+ ctrl->use_hw_closed_loop ? "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;
+ }
+ }
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH)
+ 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 && ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH)
+ 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_CPRH) {
+ cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
+ CPR4_MARGIN_ADJ_CTL_HW_CLOSED_LOOP_EN_MASK,
+ ctrl->cpr_allowed_sw && 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;
+ }
+
+ rc = msm_spm_avs_enable_irq(0, MSM_SPM_AVS_IRQ_MAX);
+ if (rc) {
+ cpr3_err(ctrl, "could not enable max IRQ, 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;
+ }
+
+ rc = msm_spm_avs_disable_irq(0, MSM_SPM_AVS_IRQ_MAX);
+ if (rc) {
+ cpr3_err(ctrl, "could not disable max IRQ, rc=%d\n",
+ rc);
+ goto done;
+ }
+ }
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ /*
+ * 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");
+ } else {
+ cpr3_debug(ctrl, "CPR mode=%s\n",
+ ctrl->cpr_allowed_sw && use_hw_closed_loop ?
+ "full HW closed-loop" : "open-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", 0644,
+ 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", 0644,
+ 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", 0444, 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", 0444,
+ 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", 0444,
+ 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", 0444,
+ 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", 0444,
+ 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", 0200,
+ 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", 0444, 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", 0444, 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", 0444, 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", 0444, 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);
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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);
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ ctrl->cpr_suspended = false;
+ rc = cpr3_regulator_update_ctrl_state(ctrl);
+ if (rc)
+ cpr3_err(ctrl, "could not enable CPR, rc=%d\n", rc);
+ } else {
+ cpr3_ctrl_loop_enable(ctrl);
+ }
+
+ mutex_unlock(&ctrl->lock);
+ return 0;
+}
+
+/**
+ * cpr3_regulator_cpu_hotplug_callback() - reset CPR IRQ affinity when a CPU is
+ * brought online via hotplug
+ * @nb: Pointer to the notifier block
+ * @action: hotplug action
+ * @hcpu: long value corresponding to the CPU number
+ *
+ * Return: NOTIFY_OK
+ */
+static int cpr3_regulator_cpu_hotplug_callback(struct notifier_block *nb,
+ unsigned long action, void *hcpu)
+{
+ struct cpr3_controller *ctrl = container_of(nb, struct cpr3_controller,
+ cpu_hotplug_notifier);
+ int cpu = (long)hcpu;
+
+ action &= ~CPU_TASKS_FROZEN;
+
+ if (action == CPU_ONLINE
+ && cpumask_test_cpu(cpu, &ctrl->irq_affinity_mask))
+ irq_set_affinity(ctrl->irq, &ctrl->irq_affinity_mask);
+
+ return NOTIFY_OK;
+}
+
+/**
+ * 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 && ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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));
+ }
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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) {
+ rc = msm_spm_probe_done();
+ if (rc) {
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(ctrl, "spm unavailable, rc=%d\n", rc);
+ return rc;
+ }
+
+ 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;
+ }
+ }
+ }
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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;
+ }
+ }
+ }
+
+ if (ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
+ 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;
+ }
+ }
+
+ if (ctrl->irq && !cpumask_empty(&ctrl->irq_affinity_mask)) {
+ irq_set_affinity(ctrl->irq, &ctrl->irq_affinity_mask);
+
+ ctrl->cpu_hotplug_notifier.notifier_call
+ = cpr3_regulator_cpu_hotplug_callback;
+ register_hotcpu_notifier(&ctrl->cpu_hotplug_notifier);
+ }
+
+ 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_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->irq && !cpumask_empty(&ctrl->irq_affinity_mask))
+ unregister_hotcpu_notifier(&ctrl->cpu_hotplug_notifier);
+
+ 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);
+
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
+ msm_spm_avs_disable_irq(0, MSM_SPM_AVS_IRQ_MAX);
+ }
+
+ 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 0000000..7dae23c
--- /dev/null
+++ b/drivers/regulator/cpr3-regulator.h
@@ -0,0 +1,1095 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __REGULATOR_CPR3_REGULATOR_H__
+#define __REGULATOR_CPR3_REGULATOR_H__
+
+#include <linux/clk.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/power/qcom/apm.h>
+#include <linux/regulator/driver.h>
+
+struct cpr3_controller;
+struct cpr3_thread;
+
+/**
+ * struct cpr3_fuse_param - defines one contiguous segment of a fuse parameter
+ * that is contained within a given row.
+ * @row: Fuse row number
+ * @bit_start: The first bit within the row of the fuse parameter segment
+ * @bit_end: The last bit within the row of the fuse parameter segment
+ *
+ * Each fuse row is 64 bits in length. bit_start and bit_end may take values
+ * from 0 to 63. bit_start must be less than or equal to bit_end.
+ */
+struct cpr3_fuse_param {
+ unsigned int row;
+ unsigned int bit_start;
+ unsigned int 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.
+ * @ldo_mode_allowed: Boolean which indicates if LDO mode is allowed for this
+ * corner. This field is applicable for CPR4 controllers
+ * that manage LDO300 supply regulator.
+ * @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 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;
+ bool ldo_mode_allowed;
+ 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;
+};
+
+/**
+ * enum cpr3_ldo_type - Constants which define the LDO supply regulator
+ * types used to manage the subsystem component rail voltage.
+ * %CPR3_LDO_KRYO: Kryo LDO regulator used to sub-regulate the HMSS
+ * per-cluster voltage.
+ * %CPR3_LDO300: LDO regulator used to sub-regulate the GFX voltage.
+ */
+enum cpr3_ldo_type {
+ CPR3_LDO_KRYO = 0,
+ CPR3_LDO300 = 1,
+};
+
+/**
+ * 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.
+ * @ldo_regulator: Pointer to the LDO supply regulator used to manage
+ * per-cluster LDO voltage and bypass state
+ * @ldo_regulator_bypass: Cached copy of the LDO regulator bypass state
+ * @ldo_ret_regulator: Pointer to the LDO retention supply regulator used to
+ * manage LDO retention bypass state
+ * @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
+ * @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
+ * @ldo_type: LDO regulator type.
+ * @ldo_min_headroom_volt: Minimum voltage difference in microvolts required
+ * between the VDD supply voltage and the LDO output in
+ * order for the LDO operate
+ * @ldo_max_headroom_volt: Maximum voltage difference in microvolts between
+ * the input and output of the active LDO hardware to
+ * maintain optimum operability.
+ * @ldo_adjust_volt: Voltage in microvolts used to offset margin assigned
+ * to IR drop between PMIC and CPU
+ * @ldo_ret_volt: The lowest supported CPU retention voltage in
+ * microvolts. This voltage may vary part-to-part based
+ * upon the value of hardware fuses.
+ * @ldo_max_volt: The maximum physically supported LDO voltage in
+ * microvolts
+ * @ldo_mode_allowed: Boolean which indicates if LDO mode is allowed for this
+ * CPR3 regulator
+ * @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,
+ * ldo_mode_allowed, 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 regulator *ldo_regulator;
+ bool ldo_regulator_bypass;
+ struct regulator *ldo_ret_regulator;
+ struct cpr3_corner *corner;
+ int corner_count;
+ struct cprh_corner_band *corner_band;
+ u32 corner_band_count;
+
+ void *platform_fuses;
+ int speed_bin_fuse;
+ int speed_bins_supported;
+ int cpr_rev_fuse;
+ 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;
+ enum cpr3_ldo_type ldo_type;
+ int ldo_min_headroom_volt;
+ int ldo_max_headroom_volt;
+ int ldo_adjust_volt;
+ int ldo_ret_volt;
+ int ldo_max_volt;
+ bool ldo_mode_allowed;
+ 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
+ * %CPR_CTRL_TYPE_CPRH: HW has CPRh controller
+ */
+enum cpr_controller_type {
+ CPR_CTRL_TYPE_CPR3,
+ CPR_CTRL_TYPE_CPR4,
+ CPR_CTRL_TYPE_CPRH,
+};
+
+/**
+ * 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.
+ * @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.
+ * @support_ldo300_vreg: Boolean value which indicates that this CPR controller
+ * manages an underlying LDO regulator of type LDO300.
+ *
+ * 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 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;
+ struct cpr3_panic_regs_info *panic_regs_info;
+ struct notifier_block panic_notifier;
+ bool support_ldo300_vreg;
+};
+
+/* 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_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_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_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_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);
+
+#else
+
+static inline int cpr3_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_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_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_limit_open_loop_voltages(struct cpr3_regulator *vreg)
+{
+ return -EPERM;
+}
+
+static inline void cpr3_open_loop_voltage_as_ceiling(
+ struct cpr3_regulator *vreg)
+{
+}
+
+static inline int cpr3_limit_floor_voltages(struct cpr3_regulator *vreg)
+{
+ return -EPERM;
+}
+
+static inline void cpr3_print_quots(struct cpr3_regulator *vreg)
+{
+}
+
+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;
+}
+
+#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 0000000..0a1a1c5
--- /dev/null
+++ b/drivers/regulator/cpr3-util.c
@@ -0,0 +1,2405 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * This file contains utility functions to be used by platform specific CPR3
+ * regulator drivers.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/cpumask.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "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_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);
+ }
+
+ /*
+ * For CPRh compliant controllers two additional corners are
+ * allocated to correspond to the APM crossover voltage and the MEM ACC
+ * crossover voltage.
+ */
+ vreg->corner = devm_kcalloc(ctrl->dev, ctrl->ctrl_type ==
+ CPR_CTRL_TYPE_CPRH ?
+ vreg->corner_count + 2 :
+ 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");
+
+ 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 if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH) {
+ /* vdd-supply is optional for CPRh controllers. */
+ ctrl->vdd_regulator = NULL;
+ } else {
+ cpr3_err(ctrl, "vdd supply is not defined\n");
+ return -ENODEV;
+ }
+
+ /*
+ * Regulator device handles are not necessary for CPRh controllers
+ * since communication with the regulators is completely managed
+ * in hardware.
+ */
+ if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPRH)
+ return rc;
+
+ 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_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_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;
+
+ if (!of_find_property(vreg->of_node,
+ "qcom,cpr-open-loop-voltage-fuse-adjustment", 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,
+ "qcom,cpr-open-loop-voltage-fuse-adjustment",
+ 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;
+
+ if (!of_find_property(vreg->of_node,
+ "qcom,cpr-closed-loop-voltage-adjustment", NULL)
+ && !of_find_property(vreg->of_node,
+ "qcom,cpr-closed-loop-voltage-fuse-adjustment", NULL)
+ && !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;
+ }
+
+ 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, "qcom,cpr-ro-scaling-factor",
+ 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,
+ "qcom,cpr-closed-loop-voltage-fuse-adjustment", NULL)) {
+ rc = cpr3_parse_array_property(vreg,
+ "qcom,cpr-closed-loop-voltage-fuse-adjustment",
+ 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,
+ "qcom,cpr-closed-loop-voltage-adjustment", NULL)) {
+ 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;
+ }
+ }
+
+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;
+ snprintf(str, sizeof(str), use_corner_band ?
+ "corner_band=%d core_config_count=%d temp_band_count=%d sdelta_size=%d\n"
+ : "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;
+
+ snprintf(str, sizeof(str), use_corner_band ?
+ "qcom,cpr-corner-band%d-temp-core-voltage-adjustment" :
+ "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), 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), 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 0000000..cfc09ba
--- /dev/null
+++ b/drivers/regulator/cpr4-apss-regulator.c
@@ -0,0 +1,1436 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#include "cpr3-regulator.h"
+
+#define MSM8953_APSS_FUSE_CORNERS 4
+
+/**
+ * struct cpr4_msm8953_apss_fuses - APSS specific fuse data for MSM8953
+ * @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_msm8953_apss_fuses {
+ u64 ro_sel[MSM8953_APSS_FUSE_CORNERS];
+ u64 init_voltage[MSM8953_APSS_FUSE_CORNERS];
+ u64 target_quot[MSM8953_APSS_FUSE_CORNERS];
+ u64 quot_offset[MSM8953_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_MSM8953_APSS_FUSE_COMBO_COUNT 64
+
+/*
+ * Constants which define the name of each fuse corner.
+ */
+enum cpr4_msm8953_apss_fuse_corner {
+ CPR4_MSM8953_APSS_FUSE_CORNER_LOWSVS = 0,
+ CPR4_MSM8953_APSS_FUSE_CORNER_SVS = 1,
+ CPR4_MSM8953_APSS_FUSE_CORNER_NOM = 2,
+ CPR4_MSM8953_APSS_FUSE_CORNER_TURBO_L1 = 3,
+};
+
+static const char * const cpr4_msm8953_apss_fuse_corner_name[] = {
+ [CPR4_MSM8953_APSS_FUSE_CORNER_LOWSVS] = "LowSVS",
+ [CPR4_MSM8953_APSS_FUSE_CORNER_SVS] = "SVS",
+ [CPR4_MSM8953_APSS_FUSE_CORNER_NOM] = "NOM",
+ [CPR4_MSM8953_APSS_FUSE_CORNER_TURBO_L1] = "TURBO_L1",
+};
+
+/*
+ * MSM8953 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 const struct cpr3_fuse_param
+msm8953_apss_ro_sel_param[MSM8953_APSS_FUSE_CORNERS][2] = {
+ {{73, 12, 15}, {} },
+ {{73, 8, 11}, {} },
+ {{73, 4, 7}, {} },
+ {{73, 0, 3}, {} },
+};
+
+static const struct cpr3_fuse_param
+msm8953_apss_init_voltage_param[MSM8953_APSS_FUSE_CORNERS][2] = {
+ {{71, 24, 29}, {} },
+ {{71, 18, 23}, {} },
+ {{71, 12, 17}, {} },
+ {{71, 6, 11}, {} },
+};
+
+static const struct cpr3_fuse_param
+msm8953_apss_target_quot_param[MSM8953_APSS_FUSE_CORNERS][2] = {
+ {{72, 44, 55}, {} },
+ {{72, 32, 43}, {} },
+ {{72, 20, 31}, {} },
+ {{72, 8, 19}, {} },
+};
+
+static const struct cpr3_fuse_param
+msm8953_apss_quot_offset_param[MSM8953_APSS_FUSE_CORNERS][2] = {
+ {{} },
+ {{71, 46, 52}, {} },
+ {{71, 39, 45}, {} },
+ {{71, 32, 38}, {} },
+};
+
+static const struct cpr3_fuse_param msm8953_cpr_fusing_rev_param[] = {
+ {71, 53, 55},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8953_apss_speed_bin_param[] = {
+ {36, 40, 42},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8953_cpr_boost_fuse_cfg_param[] = {
+ {36, 43, 45},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8953_apss_boost_fuse_volt_param[] = {
+ {71, 0, 5},
+ {},
+};
+
+static const struct cpr3_fuse_param msm8953_misc_fuse_volt_adj_param[] = {
+ {36, 54, 54},
+ {},
+};
+
+/*
+ * The number of possible values for misc fuse is
+ * 2^(#bits defined for misc fuse)
+ */
+#define MSM8953_MISC_FUSE_VAL_COUNT BIT(1)
+
+/*
+ * Open loop voltage fuse reference voltages in microvolts for MSM8953
+ */
+static const int msm8953_apss_fuse_ref_volt
+ [MSM8953_APSS_FUSE_CORNERS] = {
+ 645000,
+ 720000,
+ 865000,
+ 1065000,
+};
+
+#define MSM8953_APSS_FUSE_STEP_VOLT 10000
+#define MSM8953_APSS_VOLTAGE_FUSE_SIZE 6
+#define MSM8953_APSS_QUOT_OFFSET_SCALE 5
+
+#define MSM8953_APSS_CPR_SENSOR_COUNT 13
+
+#define MSM8953_APSS_CPR_CLOCK_RATE 19200000
+
+#define MSM8953_APSS_MAX_TEMP_POINTS 3
+#define MSM8953_APSS_TEMP_SENSOR_ID_START 4
+#define MSM8953_APSS_TEMP_SENSOR_ID_END 13
+/*
+ * Boost voltage fuse reference and ceiling voltages in microvolts for
+ * MSM8953.
+ */
+#define MSM8953_APSS_BOOST_FUSE_REF_VOLT 1140000
+#define MSM8953_APSS_BOOST_CEILING_VOLT 1140000
+#define MSM8953_APSS_BOOST_FLOOR_VOLT 900000
+#define MAX_BOOST_CONFIG_FUSE_VALUE 8
+
+#define MSM8953_APSS_CPR_SDELTA_CORE_COUNT 15
+
+/*
+ * 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};
+
+/**
+ * cpr4_msm8953_apss_read_fuse_data() - load APSS specific fuse parameter values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function allocates a cpr4_msm8953_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_msm8953_apss_read_fuse_data(struct cpr3_regulator *vreg)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ struct cpr4_msm8953_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, msm8953_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, msm8953_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, msm8953_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 >= MSM8953_MISC_FUSE_VAL_COUNT) {
+ cpr3_err(vreg, "CPR misc fuse value = %llu, should be < %lu\n",
+ fuse->misc, MSM8953_MISC_FUSE_VAL_COUNT);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < MSM8953_APSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ msm8953_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,
+ msm8953_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,
+ msm8953_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,
+ msm8953_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, msm8953_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,
+ msm8953_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_MSM8953_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 = MSM8953_APSS_FUSE_CORNERS;
+ 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)
+{
+ 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;
+}
+
+/**
+ * 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_msm8953_apss_fuses *fuse = vreg->platform_fuses;
+ int tuple_list_size = MSM8953_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_msm8953_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_msm8953_apss_calculate_open_loop_voltages(
+ struct cpr3_regulator *vreg)
+{
+ struct device_node *node = vreg->of_node;
+ struct cpr4_msm8953_apss_fuses *fuse = vreg->platform_fuses;
+ 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++) {
+ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(
+ msm8953_apss_fuse_ref_volt[i],
+ MSM8953_APSS_FUSE_STEP_VOLT, fuse->init_voltage[i],
+ MSM8953_APSS_VOLTAGE_FUSE_SIZE);
+
+ /* Log fused open-loop voltage values for debugging purposes. */
+ cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n",
+ cpr4_msm8953_apss_fuse_corner_name[i],
+ fuse_volt[i]);
+ }
+
+ 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_msm8953_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_msm8953_apss_set_no_interpolation_quotients(
+ struct cpr3_regulator *vreg, int *volt_adjust,
+ int *volt_adjust_fuse, int *ro_scale)
+{
+ struct cpr4_msm8953_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_msm8953_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_msm8953_apss_calculate_target_quotients(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr4_msm8953_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. */
+ cpr3_info(vreg, "fused LowSVS: quot[%2llu]=%4llu\n",
+ fuse->ro_sel[CPR4_MSM8953_APSS_FUSE_CORNER_LOWSVS],
+ fuse->target_quot[CPR4_MSM8953_APSS_FUSE_CORNER_LOWSVS]);
+ for (i = CPR4_MSM8953_APSS_FUSE_CORNER_SVS;
+ i <= CPR4_MSM8953_APSS_FUSE_CORNER_TURBO_L1; i++)
+ cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
+ cpr4_msm8953_apss_fuse_corner_name[i],
+ fuse->ro_sel[i], fuse->target_quot[i],
+ fuse->ro_sel[i], fuse->quot_offset[i] *
+ MSM8953_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_msm8953_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_MSM8953_APSS_FUSE_CORNER_LOWSVS;
+ 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_MSM8953_APSS_FUSE_CORNER_LOWSVS];
+ i++)
+ vreg->corner[i].target_quot[ro] = quot;
+
+ for (i = CPR4_MSM8953_APSS_FUSE_CORNER_SVS;
+ 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]
+ * MSM8953_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 > MSM8953_APSS_MAX_TEMP_POINTS) {
+ cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
+ temp_point_count, MSM8953_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 = MSM8953_APSS_TEMP_SENSOR_ID_START;
+ ctrl->temp_sensor_id_end = MSM8953_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_msm8953_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(
+ MSM8953_APSS_BOOST_FUSE_REF_VOLT,
+ MSM8953_APSS_FUSE_STEP_VOLT,
+ fuse->boost_voltage,
+ MSM8953_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, MSM8953_APSS_BOOST_CEILING_VOLT);
+ boost_voltage = max(boost_voltage, MSM8953_APSS_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 > MSM8953_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,
+ MSM8953_APSS_BOOST_CEILING_VOLT);
+ final_boost_volt = max(final_boost_volt,
+ MSM8953_APSS_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 = MSM8953_APSS_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_msm8953_apss_fuses *fuse;
+ int rc;
+
+ rc = cpr4_msm8953_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_msm8953_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_msm8953_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 >
+ MSM8953_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);
+
+ 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 = MSM8953_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->cpr_clock_rate = MSM8953_APSS_CPR_CLOCK_RATE;
+ ctrl->ctrl_type = CPR_CTRL_TYPE_CPR4;
+ ctrl->supports_hw_closed_loop = true;
+ 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 int cpr4_apss_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cpr3_controller *ctrl;
+ 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;
+
+ 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_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++) {
+ 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 const struct of_device_id cpr4_regulator_match_table[] = {
+ { .compatible = "qcom,cpr4-msm8953-apss-regulator", },
+ {}
+};
+
+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/drivers/regulator/cpr4-mmss-ldo-regulator.c b/drivers/regulator/cpr4-mmss-ldo-regulator.c
new file mode 100644
index 0000000..c4aef84
--- /dev/null
+++ b/drivers/regulator/cpr4-mmss-ldo-regulator.c
@@ -0,0 +1,769 @@
+/*
+ * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/msm-ldo-regulator.h>
+
+#include "cpr3-regulator.h"
+
+#define SDM660_MMSS_FUSE_CORNERS 6
+
+/**
+ * struct cpr4_sdm660_mmss_fuses - MMSS specific fuse data for SDM660
+ * @init_voltage: Initial (i.e. open-loop) voltage fuse parameter value
+ * for each fuse corner (raw, not converted to a voltage)
+ * @offset_voltage: The closed-loop voltage margin adjustment fuse parameter
+ * value for each fuse corner (raw, not converted to a
+ * voltage)
+ * @cpr_fusing_rev: CPR fusing revision fuse parameter value
+ * @ldo_enable: The ldo enable fuse parameter for each fuse corner
+ * indicates that VDD_GFX can be configured to LDO mode in
+ * the corresponding fuse corner.
+ * @ldo_cpr_cl_enable: A fuse parameter indicates that GFX CPR can be
+ * configured to operate in closed-loop mode when VDD_GFX
+ * is configured for LDO sub-regulated mode.
+ *
+ * This struct holds the values for all of the fuses read from memory.
+ */
+struct cpr4_sdm660_mmss_fuses {
+ u64 init_voltage[SDM660_MMSS_FUSE_CORNERS];
+ u64 offset_voltage[SDM660_MMSS_FUSE_CORNERS];
+ u64 cpr_fusing_rev;
+ u64 ldo_enable[SDM660_MMSS_FUSE_CORNERS];
+ u64 ldo_cpr_cl_enable;
+};
+
+/* Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 */
+#define CPR4_SDM660_MMSS_FUSE_COMBO_COUNT 8
+
+/*
+ * SDM660 MMSS 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 const struct cpr3_fuse_param
+sdm660_mmss_init_voltage_param[SDM660_MMSS_FUSE_CORNERS][2] = {
+ {{65, 39, 43}, {} },
+ {{65, 39, 43}, {} },
+ {{65, 34, 38}, {} },
+ {{65, 34, 38}, {} },
+ {{65, 29, 33}, {} },
+ {{65, 24, 28}, {} },
+};
+
+static const struct cpr3_fuse_param sdm660_cpr_fusing_rev_param[] = {
+ {71, 34, 36},
+ {},
+};
+
+static const struct cpr3_fuse_param
+sdm660_mmss_offset_voltage_param[SDM660_MMSS_FUSE_CORNERS][2] = {
+ {{} },
+ {{} },
+ {{} },
+ {{65, 52, 55}, {} },
+ {{65, 48, 51}, {} },
+ {{65, 44, 47}, {} },
+};
+
+static const struct cpr3_fuse_param
+sdm660_mmss_ldo_enable_param[SDM660_MMSS_FUSE_CORNERS][2] = {
+ {{73, 62, 62}, {} },
+ {{73, 61, 61}, {} },
+ {{73, 60, 60}, {} },
+ {{73, 59, 59}, {} },
+ {{73, 58, 58}, {} },
+ {{73, 57, 57}, {} },
+};
+
+static const struct cpr3_fuse_param sdm660_ldo_cpr_cl_enable_param[] = {
+ {71, 38, 38},
+ {},
+};
+
+/* Additional SDM660 specific data: */
+
+/* Open loop voltage fuse reference voltages in microvolts */
+static const int sdm660_mmss_fuse_ref_volt[SDM660_MMSS_FUSE_CORNERS] = {
+ 585000,
+ 645000,
+ 725000,
+ 790000,
+ 870000,
+ 925000,
+};
+
+#define SDM660_MMSS_FUSE_STEP_VOLT 10000
+#define SDM660_MMSS_OFFSET_FUSE_STEP_VOLT 10000
+#define SDM660_MMSS_VOLTAGE_FUSE_SIZE 5
+
+#define SDM660_MMSS_CPR_SENSOR_COUNT 11
+
+#define SDM660_MMSS_CPR_CLOCK_RATE 19200000
+
+/**
+ * cpr4_sdm660_mmss_read_fuse_data() - load MMSS specific fuse parameter
+ * values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function allocates a cpr4_sdm660_mmss_fuses struct, fills it with
+ * values read out of hardware fuses, and finally copies common fuse values
+ * into the regulator struct.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_sdm660_mmss_read_fuse_data(struct cpr3_regulator *vreg)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ struct cpr4_sdm660_mmss_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, sdm660_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, sdm660_ldo_cpr_cl_enable_param,
+ &fuse->ldo_cpr_cl_enable);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read ldo cpr closed-loop enable fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < SDM660_MMSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ sdm660_mmss_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,
+ sdm660_mmss_offset_voltage_param[i],
+ &fuse->offset_voltage[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d offset voltage fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+
+ rc = cpr3_read_fuse_param(base,
+ sdm660_mmss_ldo_enable_param[i],
+ &fuse->ldo_enable[i]);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read fuse-corner %d ldo enable fuse, rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+
+ vreg->fuse_combo = fuse->cpr_fusing_rev;
+ if (vreg->fuse_combo >= CPR4_SDM660_MMSS_FUSE_COMBO_COUNT) {
+ cpr3_err(vreg, "invalid CPR fuse combo = %d found, not in range 0 - %d\n",
+ vreg->fuse_combo,
+ CPR4_SDM660_MMSS_FUSE_COMBO_COUNT - 1);
+ return -EINVAL;
+ }
+
+ vreg->cpr_rev_fuse = fuse->cpr_fusing_rev;
+ vreg->fuse_corner_count = SDM660_MMSS_FUSE_CORNERS;
+ vreg->platform_fuses = fuse;
+
+ return 0;
+}
+
+/**
+ * cpr3_sdm660_mmss_calculate_open_loop_voltages() - calculate the open-loop
+ * voltage for each corner of a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_sdm660_mmss_calculate_open_loop_voltages(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr4_sdm660_mmss_fuses *fuse = vreg->platform_fuses;
+ int i, rc = 0;
+ const int *ref_volt;
+ int *fuse_volt;
+
+ fuse_volt = kcalloc(vreg->fuse_corner_count, sizeof(*fuse_volt),
+ GFP_KERNEL);
+ if (!fuse_volt)
+ return -ENOMEM;
+
+ ref_volt = sdm660_mmss_fuse_ref_volt;
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(ref_volt[i],
+ SDM660_MMSS_FUSE_STEP_VOLT, fuse->init_voltage[i],
+ SDM660_MMSS_VOLTAGE_FUSE_SIZE);
+ cpr3_info(vreg, "fuse_corner[%d] open-loop=%7d uV\n",
+ i, fuse_volt[i]);
+ }
+
+ 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;
+ }
+
+ for (i = 1; i < vreg->fuse_corner_count; i++) {
+ if (fuse_volt[i] < fuse_volt[i - 1]) {
+ cpr3_debug(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];
+ }
+ }
+
+ for (i = 0; i < vreg->corner_count; i++)
+ vreg->corner[i].open_loop_volt
+ = fuse_volt[vreg->corner[i].cpr_fuse_corner];
+
+ 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);
+
+done:
+ kfree(fuse_volt);
+ return rc;
+}
+
+/**
+ * cpr4_mmss_parse_ldo_mode_data() - Parse the LDO mode enable state for each
+ * corner of a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function considers 2 sets of data: one set from device node and other
+ * set from fuses and applies set intersection to decide the final LDO mode
+ * enable state of each corner. If the device node configuration is not
+ * specified, then the function applies LDO mode disable for all corners.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_mmss_parse_ldo_mode_data(struct cpr3_regulator *vreg)
+{
+ struct cpr4_sdm660_mmss_fuses *fuse = vreg->platform_fuses;
+ int i, rc = 0;
+ u32 *ldo_allowed;
+ char *prop_str = "qcom,cpr-corner-allow-ldo-mode";
+
+ if (!of_find_property(vreg->of_node, prop_str, NULL)) {
+ cpr3_debug(vreg, "%s property is missing. LDO mode is disabled for all corners\n",
+ prop_str);
+ return 0;
+ }
+
+ ldo_allowed = kcalloc(vreg->corner_count, sizeof(*ldo_allowed),
+ GFP_KERNEL);
+ if (!ldo_allowed)
+ return -ENOMEM;
+
+ rc = cpr3_parse_corner_array_property(vreg, prop_str, 1, ldo_allowed);
+ if (rc) {
+ cpr3_err(vreg, "%s read failed, rc=%d\n", prop_str, rc);
+ goto done;
+ }
+
+ for (i = 0; i < vreg->corner_count; i++)
+ vreg->corner[i].ldo_mode_allowed
+ = (ldo_allowed[i] && fuse->ldo_enable[i]);
+
+done:
+ kfree(ldo_allowed);
+ return rc;
+}
+
+/**
+ * cpr4_mmss_parse_corner_operating_mode() - Parse the CPR closed-loop operation
+ * enable state for each corner of a CPR3 regulator
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function ensures that closed-loop operation is enabled only for LDO
+ * mode allowed corners.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_mmss_parse_corner_operating_mode(struct cpr3_regulator *vreg)
+{
+ struct cpr4_sdm660_mmss_fuses *fuse = vreg->platform_fuses;
+ int i, rc = 0;
+ u32 *use_closed_loop;
+ char *prop_str = "qcom,cpr-corner-allow-closed-loop";
+
+ if (!of_find_property(vreg->of_node, prop_str, NULL)) {
+ cpr3_debug(vreg, "%s property is missing. Use open-loop for all corners\n",
+ prop_str);
+ for (i = 0; i < vreg->corner_count; i++)
+ vreg->corner[i].use_open_loop = true;
+
+ return 0;
+ }
+
+ use_closed_loop = kcalloc(vreg->corner_count, sizeof(*use_closed_loop),
+ GFP_KERNEL);
+ if (!use_closed_loop)
+ return -ENOMEM;
+
+ rc = cpr3_parse_corner_array_property(vreg, prop_str, 1,
+ use_closed_loop);
+ if (rc) {
+ cpr3_err(vreg, "%s read failed, rc=%d\n", prop_str, rc);
+ goto done;
+ }
+
+ for (i = 0; i < vreg->corner_count; i++)
+ vreg->corner[i].use_open_loop
+ = !(fuse->ldo_cpr_cl_enable && use_closed_loop[i]
+ && vreg->corner[i].ldo_mode_allowed);
+
+done:
+ kfree(use_closed_loop);
+ return rc;
+}
+
+/**
+ * cpr4_mmss_parse_corner_data() - parse MMSS corner data from device tree
+ * properties of the regulator's device node
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_mmss_parse_corner_data(struct cpr3_regulator *vreg)
+{
+ int i, rc;
+ u32 *temp;
+
+ rc = cpr3_parse_common_corner_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "error reading corner data, rc=%d\n", rc);
+ return rc;
+ }
+
+ temp = kcalloc(vreg->corner_count * CPR3_RO_COUNT, sizeof(*temp),
+ GFP_KERNEL);
+ if (!temp)
+ return -ENOMEM;
+
+ rc = cpr3_parse_corner_array_property(vreg, "qcom,cpr-target-quotients",
+ CPR3_RO_COUNT, temp);
+ if (rc) {
+ cpr3_err(vreg, "could not load target quotients, rc=%d\n", rc);
+ goto done;
+ }
+
+ for (i = 0; i < vreg->corner_count; i++)
+ memcpy(vreg->corner[i].target_quot, &temp[i * CPR3_RO_COUNT],
+ sizeof(*temp) * CPR3_RO_COUNT);
+
+done:
+ kfree(temp);
+ return rc;
+}
+
+/**
+ * cpr4_sdm660_mmss_adjust_target_quotients() - adjust the target quotients for
+ * each corner according to device tree values and fuse values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_sdm660_mmss_adjust_target_quotients(struct cpr3_regulator *vreg)
+{
+ struct cpr4_sdm660_mmss_fuses *fuse = vreg->platform_fuses;
+ const struct cpr3_fuse_param (*offset_param)[2];
+ int *volt_offset;
+ int i, fuse_len, rc = 0;
+
+ volt_offset = kcalloc(vreg->fuse_corner_count, sizeof(*volt_offset),
+ GFP_KERNEL);
+ if (!volt_offset)
+ return -ENOMEM;
+
+ offset_param = sdm660_mmss_offset_voltage_param;
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ fuse_len = offset_param[i][0].bit_end + 1
+ - offset_param[i][0].bit_start;
+ volt_offset[i] = cpr3_convert_open_loop_voltage_fuse(
+ 0, SDM660_MMSS_OFFSET_FUSE_STEP_VOLT,
+ fuse->offset_voltage[i], fuse_len);
+ if (volt_offset[i])
+ cpr3_info(vreg, "fuse_corner[%d] offset=%7d uV\n",
+ i, volt_offset[i]);
+ }
+
+ rc = cpr3_adjust_target_quotients(vreg, volt_offset);
+ if (rc)
+ cpr3_err(vreg, "adjust target quotients failed, rc=%d\n", rc);
+
+ kfree(volt_offset);
+ return rc;
+}
+
+/**
+ * cpr4_mmss_print_settings() - print out MMSS CPR configuration settings into
+ * the kernel log for debugging purposes
+ * @vreg: Pointer to the CPR3 regulator
+ */
+static void cpr4_mmss_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);
+ }
+}
+
+/**
+ * cpr4_mmss_init_thread() - perform all 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_mmss_init_thread(struct cpr3_thread *thread)
+{
+ struct cpr3_controller *ctrl = thread->ctrl;
+ struct cpr3_regulator *vreg = &thread->vreg[0];
+ int rc;
+
+ rc = cpr3_parse_common_thread_data(thread);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR thread data from device tree, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (!of_find_property(ctrl->dev->of_node, "vdd-thread0-ldo-supply",
+ NULL)) {
+ cpr3_err(vreg, "ldo supply regulator is not specified\n");
+ return -EINVAL;
+ }
+
+ vreg->ldo_regulator = devm_regulator_get(ctrl->dev, "vdd-thread0-ldo");
+ if (IS_ERR(vreg->ldo_regulator)) {
+ rc = PTR_ERR(vreg->ldo_regulator);
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(vreg, "unable to request vdd-thread0-ldo regulator, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ vreg->ldo_mode_allowed = !of_property_read_bool(vreg->of_node,
+ "qcom,ldo-disable");
+ vreg->ldo_regulator_bypass = BHS_MODE;
+ vreg->ldo_type = CPR3_LDO300;
+
+ rc = cpr4_sdm660_mmss_read_fuse_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to read CPR fuse data, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = cpr4_mmss_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 = cpr4_sdm660_mmss_adjust_target_quotients(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to adjust target quotients, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr4_sdm660_mmss_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_mmss_parse_ldo_mode_data(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to parse ldo mode data, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = cpr4_mmss_parse_corner_operating_mode(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to parse closed-loop operating mode data, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cpr4_mmss_print_settings(vreg);
+
+ return 0;
+}
+
+/**
+ * cpr4_mmss_init_controller() - perform MMSS CPR4 controller specific
+ * initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cpr4_mmss_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;
+ }
+
+ ctrl->sensor_count = SDM660_MMSS_CPR_SENSOR_COUNT;
+
+ /*
+ * MMSS only has one thread (0) 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->cpr_clock_rate = SDM660_MMSS_CPR_CLOCK_RATE;
+ ctrl->ctrl_type = CPR_CTRL_TYPE_CPR4;
+ ctrl->support_ldo300_vreg = true;
+
+ /*
+ * 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;
+
+ /* iface_clk is optional for sdm660 */
+ ctrl->iface_clk = NULL;
+ ctrl->bus_clk = devm_clk_get(ctrl->dev, "bus_clk");
+ if (IS_ERR(ctrl->bus_clk)) {
+ rc = PTR_ERR(ctrl->bus_clk);
+ if (rc != -EPROBE_DEFER)
+ cpr3_err(ctrl, "unable request bus clock, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cpr4_mmss_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cpr3_controller *ctrl;
+ int 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;
+
+ 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_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;
+ } else if (ctrl->thread[0].vreg_count != 1) {
+ cpr3_err(ctrl, "expected 1 regulator but found %d\n",
+ ctrl->thread[0].vreg_count);
+ return -EINVAL;
+ }
+
+ rc = cpr4_mmss_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_mmss_init_thread(&ctrl->thread[0]);
+ if (rc) {
+ cpr3_err(&ctrl->thread[0].vreg[0], "thread initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_mem_acc_init(&ctrl->thread[0].vreg[0]);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize mem-acc configuration, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, ctrl);
+
+ return cpr3_regulator_register(pdev, ctrl);
+}
+
+static int cpr4_mmss_regulator_remove(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_unregister(ctrl);
+}
+
+static int cpr4_mmss_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_mmss_regulator_resume(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_resume(ctrl);
+}
+
+/* Data corresponds to the SoC revision */
+static const struct of_device_id cpr4_mmss_regulator_match_table[] = {
+ {
+ .compatible = "qcom,cpr4-sdm660-mmss-ldo-regulator",
+ .data = (void *)NULL,
+ },
+ { },
+};
+
+static struct platform_driver cpr4_mmss_regulator_driver = {
+ .driver = {
+ .name = "qcom,cpr4-mmss-ldo-regulator",
+ .of_match_table = cpr4_mmss_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = cpr4_mmss_regulator_probe,
+ .remove = cpr4_mmss_regulator_remove,
+ .suspend = cpr4_mmss_regulator_suspend,
+ .resume = cpr4_mmss_regulator_resume,
+};
+
+static int cpr_regulator_init(void)
+{
+ return platform_driver_register(&cpr4_mmss_regulator_driver);
+}
+
+static void cpr_regulator_exit(void)
+{
+ platform_driver_unregister(&cpr4_mmss_regulator_driver);
+}
+
+MODULE_DESCRIPTION("CPR4 MMSS LDO regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(cpr_regulator_init);
+module_exit(cpr_regulator_exit);
diff --git a/drivers/regulator/cprh-kbss-regulator.c b/drivers/regulator/cprh-kbss-regulator.c
new file mode 100644
index 0000000..f21800e
--- /dev/null
+++ b/drivers/regulator/cprh-kbss-regulator.c
@@ -0,0 +1,2156 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#include "cpr3-regulator.h"
+
+#define MSM8998_KBSS_FUSE_CORNERS 4
+#define SDM660_KBSS_FUSE_CORNERS 5
+
+/**
+ * struct cprh_kbss_fuses - KBSS specific fuse data
+ * @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
+ * @force_highest_corner: Flag indicating that all corners must operate
+ * at the voltage of the highest corner. This is
+ * applicable to MSM8998 only.
+ * @aging_init_quot_diff: Initial quotient difference between CPR aging
+ * min and max sensors measured at time of manufacturing
+ *
+ * This struct holds the values for all of the fuses read from memory.
+ */
+struct cprh_kbss_fuses {
+ u64 *ro_sel;
+ u64 *init_voltage;
+ u64 *target_quot;
+ u64 *quot_offset;
+ u64 speed_bin;
+ u64 cpr_fusing_rev;
+ u64 force_highest_corner;
+ u64 aging_init_quot_diff;
+};
+
+/*
+ * Fuse combos 0 - 7 map to CPR fusing revision 0 - 7 with speed bin fuse = 0.
+ * Fuse combos 8 - 15 map to CPR fusing revision 0 - 7 with speed bin fuse = 1.
+ * Fuse combos 16 - 23 map to CPR fusing revision 0 - 7 with speed bin fuse = 2.
+ * Fuse combos 24 - 31 map to CPR fusing revision 0 - 7 with speed bin fuse = 3.
+ */
+#define CPRH_MSM8998_KBSS_FUSE_COMBO_COUNT 32
+#define CPRH_SDM660_KBSS_FUSE_COMBO_COUNT 16
+
+/*
+ * Constants which define the name of each fuse corner.
+ */
+enum cprh_msm8998_kbss_fuse_corner {
+ CPRH_MSM8998_KBSS_FUSE_CORNER_LOWSVS = 0,
+ CPRH_MSM8998_KBSS_FUSE_CORNER_SVS = 1,
+ CPRH_MSM8998_KBSS_FUSE_CORNER_NOM = 2,
+ CPRH_MSM8998_KBSS_FUSE_CORNER_TURBO_L1 = 3,
+};
+
+static const char * const cprh_msm8998_kbss_fuse_corner_name[] = {
+ [CPRH_MSM8998_KBSS_FUSE_CORNER_LOWSVS] = "LowSVS",
+ [CPRH_MSM8998_KBSS_FUSE_CORNER_SVS] = "SVS",
+ [CPRH_MSM8998_KBSS_FUSE_CORNER_NOM] = "NOM",
+ [CPRH_MSM8998_KBSS_FUSE_CORNER_TURBO_L1] = "TURBO_L1",
+};
+
+enum cprh_sdm660_power_kbss_fuse_corner {
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_LOWSVS = 0,
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_SVS = 1,
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_SVSPLUS = 2,
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_NOM = 3,
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_TURBO_L1 = 4,
+};
+
+static const char * const cprh_sdm660_power_kbss_fuse_corner_name[] = {
+ [CPRH_SDM660_POWER_KBSS_FUSE_CORNER_LOWSVS] = "LowSVS",
+ [CPRH_SDM660_POWER_KBSS_FUSE_CORNER_SVS] = "SVS",
+ [CPRH_SDM660_POWER_KBSS_FUSE_CORNER_SVSPLUS] = "SVSPLUS",
+ [CPRH_SDM660_POWER_KBSS_FUSE_CORNER_NOM] = "NOM",
+ [CPRH_SDM660_POWER_KBSS_FUSE_CORNER_TURBO_L1] = "TURBO_L1",
+};
+
+enum cprh_sdm660_perf_kbss_fuse_corner {
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_SVS = 0,
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_SVSPLUS = 1,
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_NOM = 2,
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_TURBO = 3,
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_TURBO_L2 = 4,
+};
+
+static const char * const cprh_sdm660_perf_kbss_fuse_corner_name[] = {
+ [CPRH_SDM660_PERF_KBSS_FUSE_CORNER_SVS] = "SVS",
+ [CPRH_SDM660_PERF_KBSS_FUSE_CORNER_SVSPLUS] = "SVSPLUS",
+ [CPRH_SDM660_PERF_KBSS_FUSE_CORNER_NOM] = "NOM",
+ [CPRH_SDM660_PERF_KBSS_FUSE_CORNER_TURBO] = "TURBO",
+ [CPRH_SDM660_PERF_KBSS_FUSE_CORNER_TURBO_L2] = "TURBO_L2",
+};
+
+/* KBSS cluster IDs */
+#define CPRH_KBSS_POWER_CLUSTER_ID 0
+#define CPRH_KBSS_PERFORMANCE_CLUSTER_ID 1
+
+/* KBSS controller IDs */
+#define CPRH_KBSS_MIN_CONTROLLER_ID 0
+#define CPRH_KBSS_MAX_CONTROLLER_ID 1
+
+/*
+ * MSM8998 KBSS fuse parameter locations:
+ *
+ * Structs are organized with the following dimensions:
+ * Outer: 0 or 1 for power or performance cluster
+ * Middle: 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 const struct cpr3_fuse_param
+msm8998_kbss_ro_sel_param[2][MSM8998_KBSS_FUSE_CORNERS][2] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{67, 12, 15}, {} },
+ {{67, 8, 11}, {} },
+ {{67, 4, 7}, {} },
+ {{67, 0, 3}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{69, 26, 29}, {} },
+ {{69, 22, 25}, {} },
+ {{69, 18, 21}, {} },
+ {{69, 14, 17}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+sdm660_kbss_ro_sel_param[2][SDM660_KBSS_FUSE_CORNERS][3] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{67, 12, 15}, {} },
+ {{67, 8, 11}, {} },
+ {{65, 56, 59}, {} },
+ {{67, 4, 7}, {} },
+ {{67, 0, 3}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{68, 61, 63}, {69, 0, 0} },
+ {{69, 1, 4}, {} },
+ {{68, 57, 60}, {} },
+ {{68, 53, 56}, {} },
+ {{66, 14, 17}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8998_kbss_init_voltage_param[2][MSM8998_KBSS_FUSE_CORNERS][2] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{67, 34, 39}, {} },
+ {{67, 28, 33}, {} },
+ {{67, 22, 27}, {} },
+ {{67, 16, 21}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{69, 48, 53}, {} },
+ {{69, 42, 47}, {} },
+ {{69, 36, 41}, {} },
+ {{69, 30, 35}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+sdm660_kbss_init_voltage_param[2][SDM660_KBSS_FUSE_CORNERS][2] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{67, 34, 39}, {} },
+ {{67, 28, 33}, {} },
+ {{71, 3, 8}, {} },
+ {{67, 22, 27}, {} },
+ {{67, 16, 21}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{69, 17, 22}, {} },
+ {{69, 23, 28}, {} },
+ {{69, 11, 16}, {} },
+ {{69, 5, 10}, {} },
+ {{70, 42, 47}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8998_kbss_target_quot_param[2][MSM8998_KBSS_FUSE_CORNERS][3] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{68, 18, 29}, {} },
+ {{68, 6, 17}, {} },
+ {{67, 58, 63}, {68, 0, 5} },
+ {{67, 46, 57}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{70, 32, 43}, {} },
+ {{70, 20, 31}, {} },
+ {{70, 8, 19}, {} },
+ {{69, 60, 63}, {70, 0, 7}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+sdm660_kbss_target_quot_param[2][SDM660_KBSS_FUSE_CORNERS][3] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{68, 12, 23}, {} },
+ {{68, 0, 11}, {} },
+ {{71, 9, 20}, {} },
+ {{67, 52, 63}, {} },
+ {{67, 40, 51}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{69, 53, 63}, {70, 0, 0}, {} },
+ {{70, 1, 12}, {} },
+ {{69, 41, 52}, {} },
+ {{69, 29, 40}, {} },
+ {{70, 48, 59}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+msm8998_kbss_quot_offset_param[2][MSM8998_KBSS_FUSE_CORNERS][3] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{} },
+ {{68, 63, 63}, {69, 0, 5}, {} },
+ {{68, 56, 62}, {} },
+ {{68, 49, 55}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{} },
+ {{71, 13, 15}, {71, 21, 24}, {} },
+ {{71, 6, 12}, {} },
+ {{70, 63, 63}, {71, 0, 5}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param
+sdm660_kbss_quot_offset_param[2][SDM660_KBSS_FUSE_CORNERS][3] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {{} },
+ {{68, 38, 44}, {} },
+ {{71, 21, 27}, {} },
+ {{68, 31, 37}, {} },
+ {{68, 24, 30}, {} },
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {{} },
+ {{70, 27, 33}, {} },
+ {{70, 20, 26}, {} },
+ {{70, 13, 19}, {} },
+ {{70, 60, 63}, {71, 0, 2}, {} },
+ },
+};
+
+static const struct cpr3_fuse_param msm8998_cpr_fusing_rev_param[] = {
+ {39, 51, 53},
+ {},
+};
+
+static const struct cpr3_fuse_param sdm660_cpr_fusing_rev_param[] = {
+ {71, 28, 30},
+ {},
+};
+
+static const struct cpr3_fuse_param kbss_speed_bin_param[] = {
+ {38, 29, 31},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8998_cpr_force_highest_corner_param[] = {
+ {100, 45, 45},
+ {},
+};
+
+static const struct cpr3_fuse_param
+msm8998_kbss_aging_init_quot_diff_param[2][2] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {69, 6, 13},
+ {},
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {71, 25, 32},
+ {},
+ },
+};
+
+static const struct cpr3_fuse_param
+sdm660_kbss_aging_init_quot_diff_param[2][2] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ {68, 45, 52},
+ {},
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ {70, 34, 41},
+ {},
+ },
+};
+
+/*
+ * Open loop voltage fuse reference voltages in microvolts for MSM8998 v1
+ */
+static const int
+msm8998_v1_kbss_fuse_ref_volt[MSM8998_KBSS_FUSE_CORNERS] = {
+ 696000,
+ 768000,
+ 896000,
+ 1112000,
+};
+
+/*
+ * Open loop voltage fuse reference voltages in microvolts for MSM8998 v2
+ */
+static const int
+msm8998_v2_kbss_fuse_ref_volt[2][MSM8998_KBSS_FUSE_CORNERS] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ 688000,
+ 756000,
+ 828000,
+ 1056000,
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ 756000,
+ 756000,
+ 828000,
+ 1056000,
+ },
+};
+
+/*
+ * Open loop voltage fuse reference voltages in microvolts for SDM660
+ */
+static const int
+sdm660_kbss_fuse_ref_volt[2][SDM660_KBSS_FUSE_CORNERS] = {
+ [CPRH_KBSS_POWER_CLUSTER_ID] = {
+ 644000,
+ 724000,
+ 788000,
+ 868000,
+ 1068000,
+ },
+ [CPRH_KBSS_PERFORMANCE_CLUSTER_ID] = {
+ 724000,
+ 788000,
+ 868000,
+ 988000,
+ 1068000,
+ },
+};
+
+#define CPRH_KBSS_FUSE_STEP_VOLT 10000
+#define CPRH_KBSS_VOLTAGE_FUSE_SIZE 6
+#define CPRH_KBSS_QUOT_OFFSET_SCALE 5
+#define CPRH_KBSS_AGING_INIT_QUOT_DIFF_SIZE 8
+#define CPRH_KBSS_AGING_INIT_QUOT_DIFF_SCALE 1
+
+#define CPRH_KBSS_CPR_CLOCK_RATE 19200000
+
+#define CPRH_KBSS_MAX_CORNER_BAND_COUNT 4
+#define CPRH_KBSS_MAX_CORNER_COUNT 40
+
+#define CPRH_KBSS_CPR_SDELTA_CORE_COUNT 4
+
+#define CPRH_KBSS_MAX_TEMP_POINTS 3
+
+/*
+ * msm8998 configuration
+ */
+#define MSM8998_KBSS_POWER_CPR_SENSOR_COUNT 6
+#define MSM8998_KBSS_PERFORMANCE_CPR_SENSOR_COUNT 9
+
+#define MSM8998_KBSS_POWER_TEMP_SENSOR_ID_START 1
+#define MSM8998_KBSS_POWER_TEMP_SENSOR_ID_END 5
+#define MSM8998_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START 6
+#define MSM8998_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END 10
+
+#define MSM8998_KBSS_POWER_AGING_SENSOR_ID 0
+#define MSM8998_KBSS_POWER_AGING_BYPASS_MASK0 0
+
+#define MSM8998_KBSS_PERFORMANCE_AGING_SENSOR_ID 0
+#define MSM8998_KBSS_PERFORMANCE_AGING_BYPASS_MASK0 0
+
+/*
+ * sdm660 configuration
+ */
+#define SDM660_KBSS_POWER_CPR_SENSOR_COUNT 6
+#define SDM660_KBSS_PERFORMANCE_CPR_SENSOR_COUNT 9
+
+#define SDM660_KBSS_POWER_TEMP_SENSOR_ID_START 10
+#define SDM660_KBSS_POWER_TEMP_SENSOR_ID_END 11
+#define SDM660_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START 4
+#define SDM660_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END 9
+
+#define SDM660_KBSS_POWER_AGING_SENSOR_ID 0
+#define SDM660_KBSS_POWER_AGING_BYPASS_MASK0 0
+
+#define SDM660_KBSS_PERFORMANCE_AGING_SENSOR_ID 0
+#define SDM660_KBSS_PERFORMANCE_AGING_BYPASS_MASK0 0
+
+/*
+ * SOC IDs
+ */
+enum soc_id {
+ MSM8998_V1_SOC_ID = 1,
+ MSM8998_V2_SOC_ID = 2,
+ SDM660_SOC_ID = 3,
+};
+
+/**
+ * cprh_msm8998_kbss_read_fuse_data() - load msm8998 KBSS specific fuse
+ * parameter values
+ * @vreg: Pointer to the CPR3 regulator
+ * @fuse: KBSS specific fuse data
+ *
+ * This function fills cprh_kbss_fuses struct with values read out of hardware
+ * fuses.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_msm8998_kbss_read_fuse_data(struct cpr3_regulator *vreg,
+ struct cprh_kbss_fuses *fuse)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ int i, id, rc;
+
+ rc = cpr3_read_fuse_param(base, msm8998_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);
+
+ id = vreg->thread->ctrl->ctrl_id;
+ for (i = 0; i < MSM8998_KBSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ msm8998_kbss_init_voltage_param[id][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,
+ msm8998_kbss_target_quot_param[id][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,
+ msm8998_kbss_ro_sel_param[id][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,
+ msm8998_kbss_quot_offset_param[id][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,
+ msm8998_kbss_aging_init_quot_diff_param[id],
+ &fuse->aging_init_quot_diff);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read aging initial quotient difference fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr3_read_fuse_param(base,
+ msm8998_cpr_force_highest_corner_param,
+ &fuse->force_highest_corner);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read CPR force highest corner fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (fuse->force_highest_corner)
+ cpr3_info(vreg, "Fusing requires all operation at the highest corner\n");
+
+ vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
+ if (vreg->fuse_combo >= CPRH_MSM8998_KBSS_FUSE_COMBO_COUNT) {
+ cpr3_err(vreg, "invalid CPR fuse combo = %d found\n",
+ vreg->fuse_combo);
+ return -EINVAL;
+ }
+
+ return rc;
+};
+
+/**
+ * cprh_sdm660_kbss_read_fuse_data() - load SDM660 KBSS specific fuse parameter
+ * values
+ * @vreg: Pointer to the CPR3 regulator
+ * @fuse: KBSS specific fuse data
+ *
+ * This function fills cprh_kbss_fuses struct with values read out of hardware
+ * fuses.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_sdm660_kbss_read_fuse_data(struct cpr3_regulator *vreg,
+ struct cprh_kbss_fuses *fuse)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ int i, id, rc;
+
+ rc = cpr3_read_fuse_param(base, sdm660_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);
+
+ id = vreg->thread->ctrl->ctrl_id;
+ for (i = 0; i < SDM660_KBSS_FUSE_CORNERS; i++) {
+ rc = cpr3_read_fuse_param(base,
+ sdm660_kbss_init_voltage_param[id][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,
+ sdm660_kbss_target_quot_param[id][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,
+ sdm660_kbss_ro_sel_param[id][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,
+ sdm660_kbss_quot_offset_param[id][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,
+ sdm660_kbss_aging_init_quot_diff_param[id],
+ &fuse->aging_init_quot_diff);
+ if (rc) {
+ cpr3_err(vreg, "Unable to read aging initial quotient difference fuse, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
+ if (vreg->fuse_combo >= CPRH_SDM660_KBSS_FUSE_COMBO_COUNT) {
+ cpr3_err(vreg, "invalid CPR fuse combo = %d found\n",
+ vreg->fuse_combo);
+ return -EINVAL;
+ }
+
+ return rc;
+};
+
+/**
+ * cprh_kbss_read_fuse_data() - load KBSS specific fuse parameter values
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * This function allocates a cprh_kbss_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 cprh_kbss_read_fuse_data(struct cpr3_regulator *vreg)
+{
+ void __iomem *base = vreg->thread->ctrl->fuse_base;
+ struct cprh_kbss_fuses *fuse;
+ int rc, fuse_corners;
+ enum soc_id soc_revision;
+
+ fuse = devm_kzalloc(vreg->thread->ctrl->dev, sizeof(*fuse), GFP_KERNEL);
+ if (!fuse)
+ return -ENOMEM;
+
+ soc_revision = vreg->thread->ctrl->soc_revision;
+ switch (soc_revision) {
+ case SDM660_SOC_ID:
+ fuse_corners = SDM660_KBSS_FUSE_CORNERS;
+ break;
+ case MSM8998_V1_SOC_ID:
+ case MSM8998_V2_SOC_ID:
+ fuse_corners = MSM8998_KBSS_FUSE_CORNERS;
+ break;
+ default:
+ cpr3_err(vreg, "unsupported soc id = %d\n", soc_revision);
+ return -EINVAL;
+ }
+
+ fuse->ro_sel = devm_kcalloc(vreg->thread->ctrl->dev, fuse_corners,
+ sizeof(*fuse->ro_sel), GFP_KERNEL);
+ fuse->init_voltage = devm_kcalloc(vreg->thread->ctrl->dev, fuse_corners,
+ sizeof(*fuse->init_voltage), GFP_KERNEL);
+ fuse->target_quot = devm_kcalloc(vreg->thread->ctrl->dev, fuse_corners,
+ sizeof(*fuse->target_quot), GFP_KERNEL);
+ fuse->quot_offset = devm_kcalloc(vreg->thread->ctrl->dev, fuse_corners,
+ sizeof(*fuse->quot_offset), GFP_KERNEL);
+
+ if (!fuse->ro_sel || !fuse->init_voltage || !fuse->target_quot
+ || !fuse->quot_offset)
+ return -ENOMEM;
+
+ rc = cpr3_read_fuse_param(base, kbss_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);
+
+ switch (soc_revision) {
+ case SDM660_SOC_ID:
+ rc = cprh_sdm660_kbss_read_fuse_data(vreg, fuse);
+ if (rc) {
+ cpr3_err(vreg, "sdm660 kbss fuse data read failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ break;
+ case MSM8998_V1_SOC_ID:
+ case MSM8998_V2_SOC_ID:
+ rc = cprh_msm8998_kbss_read_fuse_data(vreg, fuse);
+ if (rc) {
+ cpr3_err(vreg, "msm8998 kbss fuse data read failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ break;
+ default:
+ cpr3_err(vreg, "unsupported soc id = %d\n", soc_revision);
+ return -EINVAL;
+ }
+
+ vreg->speed_bin_fuse = fuse->speed_bin;
+ vreg->cpr_rev_fuse = fuse->cpr_fusing_rev;
+ vreg->fuse_corner_count = fuse_corners;
+ vreg->platform_fuses = fuse;
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_parse_corner_data() - parse KBSS 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 cprh_kbss_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;
+ }
+
+ /*
+ * A total of CPRH_KBSS_MAX_CORNER_COUNT - 1 corners
+ * may be specified in device tree as an additional corner
+ * must be allocated to correspond to the APM crossover voltage.
+ */
+ if (vreg->corner_count > CPRH_KBSS_MAX_CORNER_COUNT - 1) {
+ cpr3_err(vreg, "corner count %d exceeds supported maximum %d\n",
+ vreg->corner_count, CPRH_KBSS_MAX_CORNER_COUNT - 1);
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+/**
+ * cprh_kbss_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 cprh_kbss_calculate_open_loop_voltages(struct cpr3_regulator *vreg)
+{
+ struct device_node *node = vreg->of_node;
+ struct cprh_kbss_fuses *fuse = vreg->platform_fuses;
+ int i, j, id, rc = 0;
+ bool allow_interpolation;
+ u64 freq_low, volt_low, freq_high, volt_high;
+ const int *ref_volt;
+ int *fuse_volt;
+ int *fmax_corner;
+ const char * const *corner_name;
+ enum soc_id soc_revision;
+
+ 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;
+ }
+
+ id = vreg->thread->ctrl->ctrl_id;
+ soc_revision = vreg->thread->ctrl->soc_revision;
+
+ switch (soc_revision) {
+ case SDM660_SOC_ID:
+ ref_volt = sdm660_kbss_fuse_ref_volt[id];
+ if (id == CPRH_KBSS_POWER_CLUSTER_ID)
+ corner_name = cprh_sdm660_power_kbss_fuse_corner_name;
+ else
+ corner_name = cprh_sdm660_perf_kbss_fuse_corner_name;
+ break;
+ case MSM8998_V1_SOC_ID:
+ ref_volt = msm8998_v1_kbss_fuse_ref_volt;
+ corner_name = cprh_msm8998_kbss_fuse_corner_name;
+ break;
+ case MSM8998_V2_SOC_ID:
+ ref_volt = msm8998_v2_kbss_fuse_ref_volt[id];
+ corner_name = cprh_msm8998_kbss_fuse_corner_name;
+ break;
+ default:
+ cpr3_err(vreg, "unsupported soc id = %d\n", soc_revision);
+ rc = -EINVAL;
+ goto done;
+ }
+
+ for (i = 0; i < vreg->fuse_corner_count; i++) {
+ fuse_volt[i] = cpr3_convert_open_loop_voltage_fuse(ref_volt[i],
+ CPRH_KBSS_FUSE_STEP_VOLT, fuse->init_voltage[i],
+ CPRH_KBSS_VOLTAGE_FUSE_SIZE);
+
+ /* Log fused open-loop voltage values for debugging purposes. */
+ cpr3_info(vreg, "fused %8s: open-loop=%7d uV\n", corner_name[i],
+ fuse_volt[i]);
+ }
+
+ 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);
+ }
+
+ kfree(fuse_volt);
+ kfree(fmax_corner);
+ return rc;
+}
+
+/**
+ * cprh_msm8998_partial_binning_override() - override the voltage and quotient
+ * settings for low corners based upon special partial binning
+ * fuse values
+ *
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Some parts are not able to operate at low voltages. The force highest
+ * corner fuse specifies if a given part must operate with voltages
+ * corresponding to the highest corner.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_msm8998_partial_binning_override(struct cpr3_regulator *vreg)
+{
+ struct cprh_kbss_fuses *fuse = vreg->platform_fuses;
+ struct cpr3_corner *corner;
+ struct cpr4_sdelta *sdelta;
+ int i;
+ u32 proc_freq;
+
+ if (fuse->force_highest_corner) {
+ cpr3_info(vreg, "overriding CPR parameters for corners 0 to %d with quotients and voltages of corner %d\n",
+ vreg->corner_count - 2, vreg->corner_count - 1);
+ corner = &vreg->corner[vreg->corner_count - 1];
+ for (i = 0; i < vreg->corner_count - 1; i++) {
+ proc_freq = vreg->corner[i].proc_freq;
+ sdelta = vreg->corner[i].sdelta;
+ if (sdelta) {
+ if (sdelta->table)
+ devm_kfree(vreg->thread->ctrl->dev,
+ sdelta->table);
+ if (sdelta->boost_table)
+ devm_kfree(vreg->thread->ctrl->dev,
+ sdelta->boost_table);
+ devm_kfree(vreg->thread->ctrl->dev,
+ sdelta);
+ }
+ vreg->corner[i] = *corner;
+ vreg->corner[i].proc_freq = proc_freq;
+ }
+
+ return 0;
+ }
+
+ return 0;
+};
+
+/**
+ * cprh_kbss_parse_core_count_temp_adj_properties() - load device tree
+ * properties associated with per-corner-band and temperature
+ * voltage adjustments.
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_parse_core_count_temp_adj_properties(
+ struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct device_node *node = vreg->of_node;
+ u32 *temp, *combo_corner_bands, *speed_bin_corner_bands;
+ int rc, i, len, temp_point_count;
+
+ vreg->allow_core_count_adj = of_find_property(node,
+ "qcom,corner-band-allow-core-count-adjustment",
+ NULL);
+ vreg->allow_temp_adj = of_find_property(node,
+ "qcom,corner-band-allow-temp-adjustment",
+ NULL);
+
+ if (!vreg->allow_core_count_adj && !vreg->allow_temp_adj)
+ return 0;
+
+ combo_corner_bands = kcalloc(vreg->fuse_combos_supported,
+ sizeof(*combo_corner_bands),
+ GFP_KERNEL);
+ if (!combo_corner_bands)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(node, "qcom,cpr-corner-bands",
+ combo_corner_bands,
+ vreg->fuse_combos_supported);
+ if (rc == -EOVERFLOW) {
+ /* Single value case */
+ rc = of_property_read_u32(node, "qcom,cpr-corner-bands",
+ combo_corner_bands);
+ for (i = 1; i < vreg->fuse_combos_supported; i++)
+ combo_corner_bands[i] = combo_corner_bands[0];
+ }
+ if (rc) {
+ cpr3_err(vreg, "error reading property qcom,cpr-corner-bands, rc=%d\n",
+ rc);
+ kfree(combo_corner_bands);
+ return rc;
+ }
+
+ vreg->fuse_combo_corner_band_offset = 0;
+ vreg->fuse_combo_corner_band_sum = 0;
+ for (i = 0; i < vreg->fuse_combos_supported; i++) {
+ vreg->fuse_combo_corner_band_sum += combo_corner_bands[i];
+ if (i < vreg->fuse_combo)
+ vreg->fuse_combo_corner_band_offset +=
+ combo_corner_bands[i];
+ }
+
+ vreg->corner_band_count = combo_corner_bands[vreg->fuse_combo];
+
+ kfree(combo_corner_bands);
+
+ if (vreg->corner_band_count <= 0 ||
+ vreg->corner_band_count > CPRH_KBSS_MAX_CORNER_BAND_COUNT ||
+ vreg->corner_band_count > vreg->corner_count) {
+ cpr3_err(vreg, "invalid corner band count %d > %d (max) for %d corners\n",
+ vreg->corner_band_count,
+ CPRH_KBSS_MAX_CORNER_BAND_COUNT,
+ vreg->corner_count);
+ return -EINVAL;
+ }
+
+ vreg->speed_bin_corner_band_offset = 0;
+ vreg->speed_bin_corner_band_sum = 0;
+ if (vreg->speed_bins_supported > 0) {
+ speed_bin_corner_bands = kcalloc(vreg->speed_bins_supported,
+ sizeof(*speed_bin_corner_bands),
+ GFP_KERNEL);
+ if (!speed_bin_corner_bands)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(node,
+ "qcom,cpr-speed-bin-corner-bands",
+ speed_bin_corner_bands,
+ vreg->speed_bins_supported);
+ if (rc) {
+ cpr3_err(vreg, "error reading property qcom,cpr-speed-bin-corner-bands, rc=%d\n",
+ rc);
+ kfree(speed_bin_corner_bands);
+ return rc;
+ }
+
+ for (i = 0; i < vreg->speed_bins_supported; i++) {
+ vreg->speed_bin_corner_band_sum +=
+ speed_bin_corner_bands[i];
+ if (i < vreg->speed_bin_fuse)
+ vreg->speed_bin_corner_band_offset +=
+ speed_bin_corner_bands[i];
+ }
+
+ if (speed_bin_corner_bands[vreg->speed_bin_fuse]
+ != vreg->corner_band_count) {
+ cpr3_err(vreg, "qcom,cpr-corner-bands and qcom,cpr-speed-bin-corner-bands conflict on number of corners bands: %d vs %u\n",
+ vreg->corner_band_count,
+ speed_bin_corner_bands[vreg->speed_bin_fuse]);
+ kfree(speed_bin_corner_bands);
+ return -EINVAL;
+ }
+
+ kfree(speed_bin_corner_bands);
+ }
+
+ vreg->corner_band = devm_kcalloc(ctrl->dev,
+ vreg->corner_band_count,
+ sizeof(*vreg->corner_band),
+ GFP_KERNEL);
+
+ temp = kcalloc(vreg->corner_band_count, sizeof(*temp), GFP_KERNEL);
+
+ if (!vreg->corner_band || !temp) {
+ rc = -ENOMEM;
+ goto free_temp;
+ }
+
+ rc = cpr3_parse_corner_band_array_property(vreg,
+ "qcom,cpr-corner-band-map",
+ 1, temp);
+ if (rc) {
+ cpr3_err(vreg, "could not load corner band map, rc=%d\n",
+ rc);
+ goto free_temp;
+ }
+
+ for (i = 1; i < vreg->corner_band_count; i++) {
+ if (temp[i - 1] > temp[i]) {
+ cpr3_err(vreg, "invalid corner band mapping: band %d corner %d, band %d corner %d\n",
+ i - 1, temp[i - 1],
+ i, temp[i]);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+ }
+
+ for (i = 0; i < vreg->corner_band_count; i++)
+ vreg->corner_band[i].corner = temp[i] - CPR3_CORNER_OFFSET;
+
+ if (!of_find_property(ctrl->dev->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;
+ rc = 0;
+ goto free_temp;
+ }
+
+ if (!vreg->allow_temp_adj) {
+ rc = 0;
+ goto free_temp;
+ }
+
+ temp_point_count = len / sizeof(u32);
+ if (temp_point_count <= 0 || temp_point_count >
+ CPRH_KBSS_MAX_TEMP_POINTS) {
+ cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
+ temp_point_count, CPRH_KBSS_MAX_TEMP_POINTS);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+
+ ctrl->temp_points = devm_kcalloc(ctrl->dev, temp_point_count,
+ sizeof(*ctrl->temp_points), GFP_KERNEL);
+ if (!ctrl->temp_points) {
+ rc = -ENOMEM;
+ goto free_temp;
+ }
+ rc = of_property_read_u32_array(ctrl->dev->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);
+ goto free_temp;
+ }
+
+ 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(ctrl->dev->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);
+ goto free_temp;
+ }
+
+ 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);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+
+ switch (ctrl->soc_revision) {
+ case SDM660_SOC_ID:
+ ctrl->temp_sensor_id_start = ctrl->ctrl_id ==
+ CPRH_KBSS_POWER_CLUSTER_ID
+ ? SDM660_KBSS_POWER_TEMP_SENSOR_ID_START :
+ SDM660_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START;
+ ctrl->temp_sensor_id_end = ctrl->ctrl_id ==
+ CPRH_KBSS_POWER_CLUSTER_ID
+ ? SDM660_KBSS_POWER_TEMP_SENSOR_ID_END :
+ SDM660_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END;
+ break;
+ case MSM8998_V1_SOC_ID:
+ case MSM8998_V2_SOC_ID:
+ ctrl->temp_sensor_id_start = ctrl->ctrl_id ==
+ CPRH_KBSS_POWER_CLUSTER_ID
+ ? MSM8998_KBSS_POWER_TEMP_SENSOR_ID_START :
+ MSM8998_KBSS_PERFORMANCE_TEMP_SENSOR_ID_START;
+ ctrl->temp_sensor_id_end = ctrl->ctrl_id ==
+ CPRH_KBSS_POWER_CLUSTER_ID
+ ? MSM8998_KBSS_POWER_TEMP_SENSOR_ID_END :
+ MSM8998_KBSS_PERFORMANCE_TEMP_SENSOR_ID_END;
+ break;
+ default:
+ cpr3_err(ctrl, "unsupported soc id = %d\n", ctrl->soc_revision);
+ rc = -EINVAL;
+ goto free_temp;
+ }
+ ctrl->allow_temp_adj = true;
+
+free_temp:
+ kfree(temp);
+
+ return rc;
+}
+
+/**
+ * cprh_kbss_apm_crossover_as_corner() - introduce a corner whose floor,
+ * open-loop, and ceiling voltages correspond to the APM
+ * crossover voltage.
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * The APM corner is utilized as a crossover corner by OSM and CPRh
+ * hardware to set the VDD supply voltage during the APM switch
+ * routine.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_apm_crossover_as_corner(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr3_corner *corner;
+
+ if (!ctrl->apm_crossover_volt) {
+ /* APM voltage crossover corner not required. */
+ return 0;
+ }
+
+ corner = &vreg->corner[vreg->corner_count];
+ /*
+ * 0 MHz indicates this corner is not to be
+ * used as active DCVS set point.
+ */
+ corner->proc_freq = 0;
+ corner->floor_volt = ctrl->apm_crossover_volt;
+ corner->ceiling_volt = ctrl->apm_crossover_volt;
+ corner->open_loop_volt = ctrl->apm_crossover_volt;
+ corner->abs_ceiling_volt = ctrl->apm_crossover_volt;
+ corner->use_open_loop = true;
+ vreg->corner_count++;
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_mem_acc_crossover_as_corner() - introduce a corner whose floor,
+ * open-loop, and ceiling voltages correspond to the MEM ACC
+ * crossover voltage.
+ * @vreg: Pointer to the CPR3 regulator
+ *
+ * The MEM ACC corner is utilized as a crossover corner by OSM and CPRh
+ * hardware to set the VDD supply voltage during the MEM ACC switch
+ * routine.
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_mem_acc_crossover_as_corner(struct cpr3_regulator *vreg)
+{
+ struct cpr3_controller *ctrl = vreg->thread->ctrl;
+ struct cpr3_corner *corner;
+
+ if (!ctrl->mem_acc_crossover_volt) {
+ /* MEM ACC voltage crossover corner not required. */
+ return 0;
+ }
+
+ corner = &vreg->corner[vreg->corner_count];
+ /*
+ * 0 MHz indicates this corner is not to be
+ * used as active DCVS set point.
+ */
+ corner->proc_freq = 0;
+ corner->floor_volt = ctrl->mem_acc_crossover_volt;
+ corner->ceiling_volt = ctrl->mem_acc_crossover_volt;
+ corner->open_loop_volt = ctrl->mem_acc_crossover_volt;
+ corner->abs_ceiling_volt = ctrl->mem_acc_crossover_volt;
+ corner->use_open_loop = true;
+ vreg->corner_count++;
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_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 cprh_kbss_set_no_interpolation_quotients(struct cpr3_regulator *vreg,
+ int *volt_adjust, int *volt_adjust_fuse, int *ro_scale)
+{
+ struct cprh_kbss_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;
+}
+
+/**
+ * cprh_kbss_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 cprh_kbss_calculate_target_quotients(struct cpr3_regulator *vreg)
+{
+ struct cprh_kbss_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 lowest_fuse_corner, highest_fuse_corner;
+ const char * const *corner_name;
+
+ switch (vreg->thread->ctrl->soc_revision) {
+ case SDM660_SOC_ID:
+ if (vreg->thread->ctrl->ctrl_id == CPRH_KBSS_POWER_CLUSTER_ID) {
+ corner_name = cprh_sdm660_power_kbss_fuse_corner_name;
+ lowest_fuse_corner =
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_LOWSVS;
+ highest_fuse_corner =
+ CPRH_SDM660_POWER_KBSS_FUSE_CORNER_TURBO_L1;
+ } else {
+ corner_name = cprh_sdm660_perf_kbss_fuse_corner_name;
+ lowest_fuse_corner =
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_SVS;
+ highest_fuse_corner =
+ CPRH_SDM660_PERF_KBSS_FUSE_CORNER_TURBO_L2;
+ }
+ break;
+ case MSM8998_V1_SOC_ID:
+ case MSM8998_V2_SOC_ID:
+ corner_name = cprh_msm8998_kbss_fuse_corner_name;
+ lowest_fuse_corner =
+ CPRH_MSM8998_KBSS_FUSE_CORNER_LOWSVS;
+ highest_fuse_corner =
+ CPRH_MSM8998_KBSS_FUSE_CORNER_TURBO_L1;
+ break;
+ default:
+ cpr3_err(vreg, "unsupported soc id = %d\n",
+ vreg->thread->ctrl->soc_revision);
+ return -EINVAL;
+ }
+
+ /* Log fused quotient values for debugging purposes. */
+ cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu\n",
+ corner_name[lowest_fuse_corner],
+ fuse->ro_sel[lowest_fuse_corner],
+ fuse->target_quot[lowest_fuse_corner]);
+ for (i = lowest_fuse_corner + 1; i <= highest_fuse_corner; i++)
+ cpr3_info(vreg, "fused %8s: quot[%2llu]=%4llu, quot_offset[%2llu]=%4llu\n",
+ corner_name[i], fuse->ro_sel[i], fuse->target_quot[i],
+ fuse->ro_sel[i], fuse->quot_offset[i] *
+ CPRH_KBSS_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 (!allow_interpolation) {
+ /* Use fused target quotients for lower frequencies. */
+ return cprh_kbss_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 = lowest_fuse_corner;
+ 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[lowest_fuse_corner]; i++)
+ vreg->corner[i].target_quot[ro] = quot;
+
+ for (i = lowest_fuse_corner + 1; 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]
+ * CPRH_KBSS_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;
+}
+
+/**
+ * cprh_kbss_print_settings() - print out KBSS CPR configuration settings into
+ * the kernel log for debugging purposes
+ * @vreg: Pointer to the CPR3 regulator
+ */
+static void cprh_kbss_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);
+ }
+}
+
+/**
+ * cprh_kbss_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 cprh_kbss_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;
+}
+
+/**
+ * cprh_kbss_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 cprh_kbss_init_regulator(struct cpr3_regulator *vreg)
+{
+ struct cprh_kbss_fuses *fuse;
+ int rc;
+
+ rc = cprh_kbss_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 = cprh_kbss_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 = cprh_kbss_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;
+ }
+
+ cprh_adjust_voltages_for_apm(vreg);
+ cprh_adjust_voltages_for_mem_acc(vreg);
+
+ 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 = cprh_kbss_calculate_target_quotients(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to calculate target quotients, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_parse_core_count_temp_adj_properties(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to parse core count and temperature adjustment properties, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cpr4_parse_core_count_temp_voltage_adj(vreg, true);
+ 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 >
+ CPRH_KBSS_CPR_SDELTA_CORE_COUNT)) {
+ cpr3_err(vreg, "qcom,max-core-count has invalid value = %d\n",
+ vreg->max_core_count);
+ return -EINVAL;
+ }
+
+ rc = cprh_msm8998_partial_binning_override(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to override CPR parameters based on partial binning fuse values, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_apm_crossover_as_corner(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to introduce APM voltage crossover corner, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_mem_acc_crossover_as_corner(vreg);
+ if (rc) {
+ cpr3_err(vreg, "unable to introduce MEM ACC voltage crossover corner, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ cprh_kbss_print_settings(vreg);
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_init_aging() - perform KBSS CPRh controller specific aging
+ * initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_init_aging(struct cpr3_controller *ctrl)
+{
+ struct cprh_kbss_fuses *fuse = NULL;
+ struct cpr3_regulator *vreg;
+ u32 aging_ro_scale;
+ int i, j, rc = 0;
+
+ for (i = 0; i < ctrl->thread_count; i++) {
+ for (j = 0; j < ctrl->thread[i].vreg_count; j++) {
+ if (ctrl->thread[i].vreg[j].aging_allowed) {
+ ctrl->aging_required = true;
+ vreg = &ctrl->thread[i].vreg[j];
+ fuse = vreg->platform_fuses;
+ break;
+ }
+ }
+ }
+
+ if (!ctrl->aging_required || !fuse || !vreg)
+ return 0;
+
+ rc = cpr3_parse_array_property(vreg, "qcom,cpr-aging-ro-scaling-factor",
+ 1, &aging_ro_scale);
+ if (rc)
+ return rc;
+
+ if (aging_ro_scale == 0) {
+ cpr3_err(ctrl, "aging RO scaling factor is invalid: %u\n",
+ aging_ro_scale);
+ return -EINVAL;
+ }
+
+ ctrl->aging_vdd_mode = REGULATOR_MODE_NORMAL;
+ ctrl->aging_complete_vdd_mode = REGULATOR_MODE_IDLE;
+
+ ctrl->aging_sensor_count = 1;
+ ctrl->aging_sensor = devm_kzalloc(ctrl->dev,
+ sizeof(*ctrl->aging_sensor),
+ GFP_KERNEL);
+ if (!ctrl->aging_sensor)
+ return -ENOMEM;
+
+ switch (ctrl->soc_revision) {
+ case SDM660_SOC_ID:
+ if (ctrl->ctrl_id == CPRH_KBSS_POWER_CLUSTER_ID) {
+ ctrl->aging_sensor->sensor_id
+ = SDM660_KBSS_POWER_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0]
+ = SDM660_KBSS_POWER_AGING_BYPASS_MASK0;
+ } else {
+ ctrl->aging_sensor->sensor_id
+ = SDM660_KBSS_PERFORMANCE_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0]
+ = SDM660_KBSS_PERFORMANCE_AGING_BYPASS_MASK0;
+ }
+ break;
+ case MSM8998_V1_SOC_ID:
+ case MSM8998_V2_SOC_ID:
+ if (ctrl->ctrl_id == CPRH_KBSS_POWER_CLUSTER_ID) {
+ ctrl->aging_sensor->sensor_id
+ = MSM8998_KBSS_POWER_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0]
+ = MSM8998_KBSS_POWER_AGING_BYPASS_MASK0;
+ } else {
+ ctrl->aging_sensor->sensor_id
+ = MSM8998_KBSS_PERFORMANCE_AGING_SENSOR_ID;
+ ctrl->aging_sensor->bypass_mask[0]
+ = MSM8998_KBSS_PERFORMANCE_AGING_BYPASS_MASK0;
+ }
+ break;
+ default:
+ cpr3_err(ctrl, "unsupported soc id = %d\n", ctrl->soc_revision);
+ return -EINVAL;
+ }
+ ctrl->aging_sensor->ro_scale = aging_ro_scale;
+
+ ctrl->aging_sensor->init_quot_diff
+ = cpr3_convert_open_loop_voltage_fuse(0,
+ CPRH_KBSS_AGING_INIT_QUOT_DIFF_SCALE,
+ fuse->aging_init_quot_diff,
+ CPRH_KBSS_AGING_INIT_QUOT_DIFF_SIZE);
+
+ cpr3_debug(ctrl, "sensor %u aging init quotient diff = %d, aging RO scale = %u QUOT/V\n",
+ ctrl->aging_sensor->sensor_id,
+ ctrl->aging_sensor->init_quot_diff,
+ ctrl->aging_sensor->ro_scale);
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_init_controller() - perform KBSS CPRh controller specific
+ * initializations
+ * @ctrl: Pointer to the CPR3 controller
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_init_controller(struct cpr3_controller *ctrl)
+{
+ int rc;
+
+ ctrl->ctrl_type = CPR_CTRL_TYPE_CPRH;
+ 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-controller-id",
+ &ctrl->ctrl_id);
+ if (rc) {
+ cpr3_err(ctrl, "could not read DT property qcom,cpr-controller-id, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ if (ctrl->ctrl_id < CPRH_KBSS_MIN_CONTROLLER_ID ||
+ ctrl->ctrl_id > CPRH_KBSS_MAX_CONTROLLER_ID) {
+ cpr3_err(ctrl, "invalid qcom,cpr-controller-id specified\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,voltage-base",
+ &ctrl->base_volt);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,voltage-base, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-up-down-delay-time",
+ &ctrl->up_down_delay_time);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,cpr-up-down-delay-time, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,apm-threshold-voltage",
+ &ctrl->apm_threshold_volt);
+ if (rc) {
+ cpr3_debug(ctrl, "qcom,apm-threshold-voltage not specified\n");
+ } else {
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,apm-crossover-voltage",
+ &ctrl->apm_crossover_volt);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,apm-crossover-voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ of_property_read_u32(ctrl->dev->of_node, "qcom,apm-hysteresis-voltage",
+ &ctrl->apm_adj_volt);
+ ctrl->apm_adj_volt = CPR3_ROUND(ctrl->apm_adj_volt, ctrl->step_volt);
+
+ ctrl->saw_use_unit_mV = of_property_read_bool(ctrl->dev->of_node,
+ "qcom,cpr-saw-use-unit-mV");
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,mem-acc-threshold-voltage",
+ &ctrl->mem_acc_threshold_volt);
+ if (!rc) {
+ ctrl->mem_acc_threshold_volt
+ = CPR3_ROUND(ctrl->mem_acc_threshold_volt, ctrl->step_volt);
+
+ rc = of_property_read_u32(ctrl->dev->of_node,
+ "qcom,mem-acc-crossover-voltage",
+ &ctrl->mem_acc_crossover_volt);
+ if (rc) {
+ cpr3_err(ctrl, "error reading property qcom,mem-acc-crossover-voltage, rc=%d\n",
+ rc);
+ return rc;
+ }
+ ctrl->mem_acc_crossover_volt
+ = CPR3_ROUND(ctrl->mem_acc_crossover_volt, ctrl->step_volt);
+ }
+
+ /*
+ * Use fixed step quotient if specified otherwise use dynamically
+ * 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;
+
+ of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-voltage-settling-time",
+ &ctrl->voltage_settling_time);
+
+ of_property_read_u32(ctrl->dev->of_node,
+ "qcom,cpr-corner-switch-delay-time",
+ &ctrl->corner_switch_delay_time);
+
+ switch (ctrl->soc_revision) {
+ case SDM660_SOC_ID:
+ if (ctrl->ctrl_id == CPRH_KBSS_POWER_CLUSTER_ID)
+ ctrl->sensor_count =
+ SDM660_KBSS_POWER_CPR_SENSOR_COUNT;
+ else
+ ctrl->sensor_count =
+ SDM660_KBSS_PERFORMANCE_CPR_SENSOR_COUNT;
+ break;
+ case MSM8998_V1_SOC_ID:
+ case MSM8998_V2_SOC_ID:
+ if (ctrl->ctrl_id == CPRH_KBSS_POWER_CLUSTER_ID)
+ ctrl->sensor_count =
+ MSM8998_KBSS_POWER_CPR_SENSOR_COUNT;
+ else
+ ctrl->sensor_count =
+ MSM8998_KBSS_PERFORMANCE_CPR_SENSOR_COUNT;
+ break;
+ default:
+ cpr3_err(ctrl, "unsupported soc id = %d\n", ctrl->soc_revision);
+ return -EINVAL;
+ }
+
+ /*
+ * KBSS 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->cpr_clock_rate = CPRH_KBSS_CPR_CLOCK_RATE;
+ ctrl->supports_hw_closed_loop = true;
+ ctrl->use_hw_closed_loop = of_property_read_bool(ctrl->dev->of_node,
+ "qcom,cpr-hw-closed-loop");
+
+ return 0;
+}
+
+/**
+ * cprh_kbss_populate_opp_table() - populate an Operating Performance Point
+ * table with the frequencies associated with each corner.
+ * This table may be used to resolve corner to frequency to
+ * open-loop voltage mappings.
+ * @pdev: Pointer to the platform device
+ *
+ * Return: 0 on success, errno on failure
+ */
+static int cprh_kbss_populate_opp_table(struct cpr3_controller *ctrl)
+{
+ struct device *dev = ctrl->dev;
+ struct cpr3_regulator *vreg = &ctrl->thread[0].vreg[0];
+ struct cpr3_corner *corner;
+ int rc, i;
+
+ for (i = 0; i < vreg->corner_count; i++) {
+ corner = &vreg->corner[i];
+ if (!corner->proc_freq) {
+ /*
+ * 0 MHz indicates this corner is not to be
+ * used as active DCVS set point. Don't add it
+ * to the OPP table.
+ */
+ continue;
+ }
+ rc = dev_pm_opp_add(dev, corner->proc_freq, i + 1);
+ if (rc) {
+ cpr3_err(ctrl, "could not add OPP for corner %d with frequency %u MHz, rc=%d\n",
+ i + 1, corner->proc_freq, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int cprh_kbss_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 cprh_kbss_regulator_resume(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_resume(ctrl);
+}
+
+/* Data corresponds to the SoC revision */
+static const struct of_device_id cprh_regulator_match_table[] = {
+ {
+ .compatible = "qcom,cprh-msm8998-v1-kbss-regulator",
+ .data = (void *)(uintptr_t)MSM8998_V1_SOC_ID,
+ },
+ {
+ .compatible = "qcom,cprh-msm8998-v2-kbss-regulator",
+ .data = (void *)(uintptr_t)MSM8998_V2_SOC_ID,
+ },
+ {
+ .compatible = "qcom,cprh-msm8998-kbss-regulator",
+ .data = (void *)(uintptr_t)MSM8998_V2_SOC_ID,
+ },
+ {
+ .compatible = "qcom,cprh-sdm660-kbss-regulator",
+ .data = (void *)(uintptr_t)SDM660_SOC_ID,
+ },
+ {}
+};
+
+static int cprh_kbss_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct cpr3_controller *ctrl;
+ int 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;
+
+ ctrl->dev = dev;
+ 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;
+ }
+
+ match = of_match_node(cprh_regulator_match_table, dev->of_node);
+ if (match)
+ ctrl->soc_revision = (uintptr_t)match->data;
+ else
+ cpr3_err(ctrl, "could not find compatible string match\n");
+
+ rc = cpr3_map_fuse_base(ctrl, pdev);
+ if (rc) {
+ cpr3_err(ctrl, "could not map fuse base address\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;
+ } else if (ctrl->thread[0].vreg_count != 1) {
+ cpr3_err(ctrl, "expected 1 regulator but found %d\n",
+ ctrl->thread[0].vreg_count);
+ return -EINVAL;
+ }
+
+ rc = cprh_kbss_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 = cprh_kbss_init_thread(&ctrl->thread[0]);
+ if (rc) {
+ cpr3_err(ctrl, "thread initialization failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_init_regulator(&ctrl->thread[0].vreg[0]);
+ if (rc) {
+ cpr3_err(&ctrl->thread[0].vreg[0], "regulator initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = cprh_kbss_init_aging(ctrl);
+ if (rc) {
+ cpr3_err(ctrl, "failed to initialize aging configurations, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, ctrl);
+
+ rc = cprh_kbss_populate_opp_table(ctrl);
+ if (rc)
+ panic("cprh-kbss-regulator OPP table initialization failed\n");
+
+ return cpr3_regulator_register(pdev, ctrl);
+}
+
+static int cprh_kbss_regulator_remove(struct platform_device *pdev)
+{
+ struct cpr3_controller *ctrl = platform_get_drvdata(pdev);
+
+ return cpr3_regulator_unregister(ctrl);
+}
+
+static struct platform_driver cprh_kbss_regulator_driver = {
+ .driver = {
+ .name = "qcom,cprh-kbss-regulator",
+ .of_match_table = cprh_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = cprh_kbss_regulator_probe,
+ .remove = cprh_kbss_regulator_remove,
+ .suspend = cprh_kbss_regulator_suspend,
+ .resume = cprh_kbss_regulator_resume,
+};
+
+static int cpr_regulator_init(void)
+{
+ return platform_driver_register(&cprh_kbss_regulator_driver);
+}
+
+static void cpr_regulator_exit(void)
+{
+ platform_driver_unregister(&cprh_kbss_regulator_driver);
+}
+
+MODULE_DESCRIPTION("CPRh KBSS regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(cpr_regulator_init);
+module_exit(cpr_regulator_exit);
diff --git a/drivers/regulator/kryo-regulator.c b/drivers/regulator/kryo-regulator.c
new file mode 100644
index 0000000..fd853e7
--- /dev/null
+++ b/drivers/regulator/kryo-regulator.c
@@ -0,0 +1,1105 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/cpu_pm.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/msm-ldo-regulator.h>
+
+#include <soc/qcom/spm.h>
+
+#define KRYO_REGULATOR_DRIVER_NAME "kryo-regulator"
+
+#define kvreg_err(kvreg, message, ...) \
+ pr_err("%s: " message, (kvreg)->name, ##__VA_ARGS__)
+#define kvreg_info(kvreg, message, ...) \
+ pr_info("%s: " message, (kvreg)->name, ##__VA_ARGS__)
+#define kvreg_debug(kvreg, message, ...) \
+ pr_debug("%s: " message, (kvreg)->name, ##__VA_ARGS__)
+
+/* CPUSS power domain register offsets */
+#define APCC_PWR_CTL_OVERRIDE 0x38
+#define APCC_PGS_RET_STATUS 0xe0
+
+/* APCS CSR register offsets */
+#define APCS_VERSION 0xfd0
+
+/* Cluster power domain register offsets */
+#define APC_LDO_VREF_SET 0x08
+#define APC_RET_VREF_SET 0x10
+#define APC_PWR_GATE_MODE 0x18
+#define APC_PWR_GATE_DLY 0x28
+#define APC_LDO_CFG 0x40
+#define APC_APM_CFG 0x50
+#define APC_PGSCTL_STS 0x60
+
+/* Register bit mask definitions*/
+#define PWR_GATE_SWITCH_MODE_MASK GENMASK(0, 0)
+#define VREF_MASK GENMASK(6, 0)
+#define APM_CFG_MASK GENMASK(7, 0)
+#define FSM_CUR_STATE_MASK GENMASK(5, 4)
+#define APC_PWR_GATE_DLY_MASK GENMASK(11, 0)
+#define APCC_PGS_MASK(cluster) (0x7 << (0x3 * (cluster)))
+
+/* Register bit definitions */
+#define VREF_BIT_POS 0
+
+/* Maximum delay to wait before declaring a Power Gate Switch timed out */
+#define PWR_GATE_SWITCH_TIMEOUT_US 5
+
+#define PWR_GATE_SWITCH_MODE_LDO 0
+#define PWR_GATE_SWITCH_MODE_BHS 1
+#define MSM8996_CPUSS_VER_1P1 0x10010000
+
+#define LDO_N_VOLTAGES 0x80
+#define AFFINITY_LEVEL_M3 2
+#define SHARED_CPU_REG_NUM 0
+#define VDD_SUPPLY_STEP_UV 5000
+#define VDD_SUPPLY_MIN_UV 80000
+
+struct kryo_regulator {
+ struct list_head link;
+ spinlock_t slock;
+ struct regulator_desc desc;
+ struct regulator_dev *rdev;
+ struct regulator_dev *retention_rdev;
+ struct regulator_desc retention_desc;
+ const char *name;
+ enum msm_ldo_supply_mode mode;
+ enum msm_ldo_supply_mode retention_mode;
+ enum msm_ldo_supply_mode pre_lpm_state_mode;
+ void __iomem *reg_base;
+ void __iomem *pm_apcc_base;
+ struct dentry *debugfs;
+ struct notifier_block cpu_pm_notifier;
+ unsigned long lpm_enter_count;
+ unsigned long lpm_exit_count;
+ int volt;
+ int retention_volt;
+ int headroom_volt;
+ int pre_lpm_state_volt;
+ int vref_func_step_volt;
+ int vref_func_min_volt;
+ int vref_func_max_volt;
+ int vref_ret_step_volt;
+ int vref_ret_min_volt;
+ int vref_ret_max_volt;
+ int cluster_num;
+ u32 ldo_config_init;
+ u32 apm_config_init;
+ u32 version;
+ bool vreg_en;
+};
+
+static struct dentry *kryo_debugfs_base;
+static DEFINE_MUTEX(kryo_regulator_list_mutex);
+static LIST_HEAD(kryo_regulator_list);
+
+static bool is_between(int left, int right, int value)
+{
+ if (left >= right && left >= value && value >= right)
+ return true;
+ if (left <= right && left <= value && value <= right)
+ return true;
+
+ return false;
+}
+
+static void kryo_masked_write(struct kryo_regulator *kvreg,
+ int reg, u32 mask, u32 val)
+{
+ u32 reg_val;
+
+ reg_val = readl_relaxed(kvreg->reg_base + reg);
+ reg_val &= ~mask;
+ reg_val |= (val & mask);
+
+ writel_relaxed(reg_val, kvreg->reg_base + reg);
+
+ /* Ensure write above completes */
+ mb();
+}
+
+static inline void kryo_pm_apcc_masked_write(struct kryo_regulator *kvreg,
+ int reg, u32 mask, u32 val)
+{
+ u32 reg_val, orig_val;
+
+ reg_val = orig_val = readl_relaxed(kvreg->pm_apcc_base + reg);
+ reg_val &= ~mask;
+ reg_val |= (val & mask);
+
+ if (reg_val != orig_val) {
+ writel_relaxed(reg_val, kvreg->pm_apcc_base + reg);
+
+ /* Ensure write above completes */
+ mb();
+ }
+}
+
+static inline int kryo_decode_retention_volt(struct kryo_regulator *kvreg,
+ int reg)
+{
+ return kvreg->vref_ret_min_volt + reg * kvreg->vref_ret_step_volt;
+}
+
+static inline int kryo_encode_retention_volt(struct kryo_regulator *kvreg,
+ int volt)
+{
+ int encoded_volt = DIV_ROUND_UP(volt - kvreg->vref_ret_min_volt,
+ kvreg->vref_ret_step_volt);
+
+ if (encoded_volt >= LDO_N_VOLTAGES || encoded_volt < 0)
+ return -EINVAL;
+ else
+ return encoded_volt;
+}
+
+static inline int kryo_decode_functional_volt(struct kryo_regulator *kvreg,
+ int reg)
+{
+ return kvreg->vref_func_min_volt + reg * kvreg->vref_func_step_volt;
+}
+
+static inline int kryo_encode_functional_volt(struct kryo_regulator *kvreg,
+ int volt)
+{
+ int encoded_volt = DIV_ROUND_UP(volt - kvreg->vref_func_min_volt,
+ kvreg->vref_func_step_volt);
+
+ if (encoded_volt >= LDO_N_VOLTAGES || encoded_volt < 0)
+ return -EINVAL;
+ else
+ return encoded_volt;
+}
+
+/* Locks must be held by the caller */
+static int kryo_set_retention_volt(struct kryo_regulator *kvreg, int volt)
+{
+ int reg_val;
+
+ reg_val = kryo_encode_retention_volt(kvreg, volt);
+ if (reg_val < 0) {
+ kvreg_err(kvreg, "unsupported LDO retention voltage, rc=%d\n",
+ reg_val);
+ return reg_val;
+ }
+
+ kryo_masked_write(kvreg, APC_RET_VREF_SET, VREF_MASK,
+ reg_val << VREF_BIT_POS);
+
+ kvreg->retention_volt = kryo_decode_retention_volt(kvreg, reg_val);
+ kvreg_debug(kvreg, "Set LDO retention voltage=%d uV (0x%x)\n",
+ kvreg->retention_volt, reg_val);
+
+ return 0;
+}
+
+/* Locks must be held by the caller */
+static int kryo_set_ldo_volt(struct kryo_regulator *kvreg, int volt)
+{
+ int reg_val;
+
+ /*
+ * Assume the consumer ensures the requested voltage satisfies the
+ * headroom and adjustment voltage requirements. The value may be
+ * rounded up if necessary, to match the LDO resolution. Configure it.
+ */
+ reg_val = kryo_encode_functional_volt(kvreg, volt);
+ if (reg_val < 0) {
+ kvreg_err(kvreg, "unsupported LDO functional voltage, rc=%d\n",
+ reg_val);
+ return reg_val;
+ }
+
+ kryo_masked_write(kvreg, APC_LDO_VREF_SET, VREF_MASK,
+ reg_val << VREF_BIT_POS);
+
+ kvreg->volt = kryo_decode_functional_volt(kvreg, reg_val);
+ kvreg_debug(kvreg, "Set LDO voltage=%d uV (0x%x)\n",
+ kvreg->volt, reg_val);
+
+ return 0;
+}
+
+/* Locks must be held by the caller */
+static int kryo_configure_mode(struct kryo_regulator *kvreg,
+ enum msm_ldo_supply_mode mode)
+{
+ u32 reg;
+ int timeout = PWR_GATE_SWITCH_TIMEOUT_US;
+
+ /* Configure LDO or BHS mode */
+ kryo_masked_write(kvreg, APC_PWR_GATE_MODE, PWR_GATE_SWITCH_MODE_MASK,
+ mode == LDO_MODE ? PWR_GATE_SWITCH_MODE_LDO
+ : PWR_GATE_SWITCH_MODE_BHS);
+
+ /* Complete register write before reading HW status register */
+ mb();
+
+ /* Delay to allow Power Gate Switch FSM to reach idle state */
+ while (timeout > 0) {
+ reg = readl_relaxed(kvreg->reg_base + APC_PGSCTL_STS);
+ if (!(reg & FSM_CUR_STATE_MASK))
+ break;
+
+ udelay(1);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ kvreg_err(kvreg, "PGS switch to %s failed. APC_PGSCTL_STS=0x%x\n",
+ mode == LDO_MODE ? "LDO" : "BHS", reg);
+ return -ETIMEDOUT;
+ }
+
+ kvreg->mode = mode;
+ kvreg_debug(kvreg, "using %s mode\n", mode == LDO_MODE ? "LDO" : "BHS");
+
+ return 0;
+}
+
+static int kryo_regulator_enable(struct regulator_dev *rdev)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+ int rc;
+ unsigned long flags;
+
+ if (kvreg->vreg_en == true)
+ return 0;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+ rc = kryo_set_ldo_volt(kvreg, kvreg->volt);
+ if (rc) {
+ kvreg_err(kvreg, "set voltage failed, rc=%d\n", rc);
+ goto done;
+ }
+
+ kvreg->vreg_en = true;
+ kvreg_debug(kvreg, "enabled\n");
+
+done:
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return rc;
+}
+
+static int kryo_regulator_disable(struct regulator_dev *rdev)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+ int rc;
+ unsigned long flags;
+
+ if (kvreg->vreg_en == false)
+ return 0;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+ kvreg->vreg_en = false;
+ kvreg_debug(kvreg, "disabled\n");
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return rc;
+}
+
+static int kryo_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ return kvreg->vreg_en;
+}
+
+static int kryo_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_volt, int max_volt, unsigned int *selector)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+ int rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+
+ if (!kvreg->vreg_en) {
+ kvreg->volt = min_volt;
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+ return 0;
+ }
+
+ rc = kryo_set_ldo_volt(kvreg, min_volt);
+ if (rc)
+ kvreg_err(kvreg, "set voltage failed, rc=%d\n", rc);
+
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return rc;
+}
+
+static int kryo_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ return kvreg->volt;
+}
+
+static int kryo_regulator_set_bypass(struct regulator_dev *rdev,
+ bool enable)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+ int rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+
+ /*
+ * LDO Vref voltage must be programmed before switching
+ * modes to ensure stable operation.
+ */
+ rc = kryo_set_ldo_volt(kvreg, kvreg->volt);
+ if (rc)
+ kvreg_err(kvreg, "set voltage failed, rc=%d\n", rc);
+
+ rc = kryo_configure_mode(kvreg, enable);
+ if (rc)
+ kvreg_err(kvreg, "could not configure to %s mode\n",
+ enable == LDO_MODE ? "LDO" : "BHS");
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return rc;
+}
+
+static int kryo_regulator_get_bypass(struct regulator_dev *rdev,
+ bool *enable)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ *enable = kvreg->mode;
+
+ return 0;
+}
+
+static int kryo_regulator_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ if (selector < kvreg->desc.n_voltages)
+ return kryo_decode_functional_volt(kvreg, selector);
+ else
+ return 0;
+}
+
+static int kryo_regulator_retention_set_voltage(struct regulator_dev *rdev,
+ int min_volt, int max_volt, unsigned int *selector)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+ int rc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+ rc = kryo_set_retention_volt(kvreg, min_volt);
+ if (rc)
+ kvreg_err(kvreg, "set voltage failed, rc=%d\n", rc);
+
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return rc;
+}
+
+static int kryo_regulator_retention_get_voltage(struct regulator_dev *rdev)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ return kvreg->retention_volt;
+}
+
+static int kryo_regulator_retention_set_bypass(struct regulator_dev *rdev,
+ bool enable)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+ int timeout = PWR_GATE_SWITCH_TIMEOUT_US;
+ int rc = 0;
+ u32 reg_val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+
+ kryo_pm_apcc_masked_write(kvreg,
+ APCC_PWR_CTL_OVERRIDE,
+ APCC_PGS_MASK(kvreg->cluster_num),
+ enable ?
+ 0 : APCC_PGS_MASK(kvreg->cluster_num));
+
+ /* Ensure write above completes before proceeding */
+ mb();
+
+ if (enable == BHS_MODE && kvreg->version < MSM8996_CPUSS_VER_1P1) {
+ /* No status register, delay worst case */
+ udelay(PWR_GATE_SWITCH_TIMEOUT_US);
+ } else if (enable == BHS_MODE) {
+ while (timeout > 0) {
+ reg_val = readl_relaxed(kvreg->pm_apcc_base
+ + APCC_PGS_RET_STATUS);
+ if (!(reg_val & APCC_PGS_MASK(kvreg->cluster_num)))
+ break;
+
+ udelay(1);
+ timeout--;
+ }
+
+ if (timeout == 0) {
+ kvreg_err(kvreg, "PGS switch timed out. APCC_PGS_RET_STATUS=0x%x\n",
+ reg_val);
+ rc = -ETIMEDOUT;
+ goto done;
+ }
+ }
+
+ /* Bypassed LDO retention operation == disallow LDO retention */
+ kvreg_debug(kvreg, "%s LDO retention\n",
+ enable ? "enabled" : "disabled");
+ kvreg->retention_mode = enable == LDO_MODE ? LDO_MODE
+ : BHS_MODE;
+
+done:
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return rc;
+}
+
+static int kryo_regulator_retention_get_bypass(struct regulator_dev *rdev,
+ bool *enable)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ *enable = kvreg->retention_mode;
+
+ return 0;
+}
+
+static int kryo_regulator_retention_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct kryo_regulator *kvreg = rdev_get_drvdata(rdev);
+
+ if (selector < kvreg->retention_desc.n_voltages)
+ return kryo_decode_retention_volt(kvreg, selector);
+ else
+ return 0;
+}
+
+static struct regulator_ops kryo_regulator_ops = {
+ .enable = kryo_regulator_enable,
+ .disable = kryo_regulator_disable,
+ .is_enabled = kryo_regulator_is_enabled,
+ .set_voltage = kryo_regulator_set_voltage,
+ .get_voltage = kryo_regulator_get_voltage,
+ .set_bypass = kryo_regulator_set_bypass,
+ .get_bypass = kryo_regulator_get_bypass,
+ .list_voltage = kryo_regulator_list_voltage,
+};
+
+static struct regulator_ops kryo_regulator_retention_ops = {
+ .set_voltage = kryo_regulator_retention_set_voltage,
+ .get_voltage = kryo_regulator_retention_get_voltage,
+ .set_bypass = kryo_regulator_retention_set_bypass,
+ .get_bypass = kryo_regulator_retention_get_bypass,
+ .list_voltage = kryo_regulator_retention_list_voltage,
+};
+
+static void kryo_ldo_voltage_init(struct kryo_regulator *kvreg)
+{
+ kryo_set_retention_volt(kvreg, kvreg->retention_volt);
+ kryo_set_ldo_volt(kvreg, kvreg->volt);
+}
+
+#define APC_PWR_GATE_DLY_INIT 0x00000101
+static int kryo_hw_init(struct kryo_regulator *kvreg)
+{
+ /* Set up VREF_LDO and VREF_RET */
+ kryo_ldo_voltage_init(kvreg);
+
+ /* Program LDO and APM configuration registers */
+ writel_relaxed(kvreg->ldo_config_init, kvreg->reg_base + APC_LDO_CFG);
+
+ kryo_masked_write(kvreg, APC_APM_CFG, APM_CFG_MASK,
+ kvreg->apm_config_init);
+
+ /* Configure power gate sequencer delay */
+ kryo_masked_write(kvreg, APC_PWR_GATE_DLY, APC_PWR_GATE_DLY_MASK,
+ APC_PWR_GATE_DLY_INIT);
+
+ /* Allow LDO retention mode only when it's safe to do so */
+ kryo_pm_apcc_masked_write(kvreg,
+ APCC_PWR_CTL_OVERRIDE,
+ APCC_PGS_MASK(kvreg->cluster_num),
+ APCC_PGS_MASK(kvreg->cluster_num));
+
+ /* Complete the above writes before other accesses */
+ mb();
+
+ return 0;
+}
+
+static ssize_t kryo_dbg_mode_read(struct file *file, char __user *buff,
+ size_t count, loff_t *ppos)
+{
+ struct kryo_regulator *kvreg = file->private_data;
+ char buf[10];
+ int len = 0;
+ u32 reg_val;
+ unsigned long flags;
+
+ if (!kvreg)
+ return -ENODEV;
+
+ /* Confirm HW state matches Kryo regulator device state */
+ spin_lock_irqsave(&kvreg->slock, flags);
+ reg_val = readl_relaxed(kvreg->reg_base + APC_PWR_GATE_MODE);
+ if (((reg_val & PWR_GATE_SWITCH_MODE_MASK) == PWR_GATE_SWITCH_MODE_LDO
+ && kvreg->mode != LDO_MODE) ||
+ ((reg_val & PWR_GATE_SWITCH_MODE_MASK) == PWR_GATE_SWITCH_MODE_BHS
+ && kvreg->mode != BHS_MODE)) {
+ kvreg_err(kvreg, "HW state disagrees on PWR gate mode! reg=0x%x\n",
+ reg_val);
+ len = snprintf(buf, sizeof(buf), "ERR\n");
+ } else {
+ len = snprintf(buf, sizeof(buf), "%s\n",
+ kvreg->mode == LDO_MODE ?
+ "LDO" : "BHS");
+ }
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return simple_read_from_buffer(buff, count, ppos, buf, len);
+}
+
+static int kryo_dbg_base_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+static const struct file_operations kryo_dbg_mode_fops = {
+ .open = kryo_dbg_base_open,
+ .read = kryo_dbg_mode_read,
+};
+
+static void kryo_debugfs_init(struct kryo_regulator *kvreg)
+{
+ struct dentry *temp;
+
+ if (IS_ERR_OR_NULL(kryo_debugfs_base)) {
+ if (PTR_ERR(kryo_debugfs_base) != -ENODEV)
+ kvreg_err(kvreg, "Base directory missing, cannot create debugfs nodes rc=%ld\n",
+ PTR_ERR(kryo_debugfs_base));
+ return;
+ }
+
+ kvreg->debugfs = debugfs_create_dir(kvreg->name, kryo_debugfs_base);
+
+ if (IS_ERR_OR_NULL(kvreg->debugfs)) {
+ kvreg_err(kvreg, "debugfs directory creation failed rc=%ld\n",
+ PTR_ERR(kvreg->debugfs));
+ return;
+ }
+
+ temp = debugfs_create_file("mode", 0444, kvreg->debugfs,
+ kvreg, &kryo_dbg_mode_fops);
+
+ if (IS_ERR_OR_NULL(temp)) {
+ kvreg_err(kvreg, "mode node creation failed rc=%ld\n",
+ PTR_ERR(temp));
+ return;
+ }
+}
+
+static void kryo_debugfs_deinit(struct kryo_regulator *kvreg)
+{
+ debugfs_remove_recursive(kvreg->debugfs);
+}
+
+static void kryo_debugfs_base_init(void)
+{
+ kryo_debugfs_base = debugfs_create_dir(KRYO_REGULATOR_DRIVER_NAME,
+ NULL);
+ if (IS_ERR_OR_NULL(kryo_debugfs_base)) {
+ if (PTR_ERR(kryo_debugfs_base) != -ENODEV)
+ pr_err("%s debugfs base directory creation failed rc=%ld\n",
+ KRYO_REGULATOR_DRIVER_NAME,
+ PTR_ERR(kryo_debugfs_base));
+ }
+}
+
+static void kryo_debugfs_base_remove(void)
+{
+ debugfs_remove_recursive(kryo_debugfs_base);
+}
+
+static int kryo_regulator_init_data(struct platform_device *pdev,
+ struct kryo_regulator *kvreg)
+{
+ int rc = 0;
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ void __iomem *temp;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pm-apc");
+ if (!res) {
+ dev_err(dev, "PM APC register address missing\n");
+ return -EINVAL;
+ }
+
+ kvreg->reg_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!kvreg->reg_base) {
+ dev_err(dev, "failed to map PM APC registers\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pm-apcc");
+ if (!res) {
+ dev_err(dev, "PM APCC register address missing\n");
+ return -EINVAL;
+ }
+
+ kvreg->pm_apcc_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!kvreg->pm_apcc_base) {
+ dev_err(dev, "failed to map PM APCC 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;
+ }
+
+ temp = ioremap(res->start, resource_size(res));
+ if (!temp) {
+ dev_err(dev, "failed to map APCS CSR registers\n");
+ return -ENOMEM;
+ }
+
+ kvreg->version = readl_relaxed(temp + APCS_VERSION);
+ iounmap(temp);
+
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,vref-functional-step-voltage",
+ &kvreg->vref_func_step_volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,vref-functional-step-voltage missing rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,vref-functional-min-voltage",
+ &kvreg->vref_func_min_volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,vref-functional-min-voltage missing rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ kvreg->vref_func_max_volt = kryo_decode_functional_volt(kvreg,
+ LDO_N_VOLTAGES - 1);
+
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,vref-retention-step-voltage",
+ &kvreg->vref_ret_step_volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,vref-retention-step-voltage missing rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,vref-retention-min-voltage",
+ &kvreg->vref_ret_min_volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,vref-retention-min-voltage missing rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ kvreg->vref_ret_max_volt = kryo_decode_retention_volt(kvreg,
+ LDO_N_VOLTAGES - 1);
+
+ rc = of_property_read_u32(dev->of_node, "qcom,ldo-default-voltage",
+ &kvreg->volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,ldo-default-voltage missing rc=%d\n", rc);
+ return rc;
+ }
+ if (!is_between(kvreg->vref_func_min_volt,
+ kvreg->vref_func_max_volt,
+ kvreg->volt)) {
+ dev_err(dev, "qcom,ldo-default-voltage=%d uV outside allowed range\n",
+ kvreg->volt);
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(dev->of_node, "qcom,retention-voltage",
+ &kvreg->retention_volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,retention-voltage missing rc=%d\n", rc);
+ return rc;
+ }
+ if (!is_between(kvreg->vref_ret_min_volt,
+ kvreg->vref_ret_max_volt,
+ kvreg->retention_volt)) {
+ dev_err(dev, "qcom,retention-voltage=%d uV outside allowed range\n",
+ kvreg->retention_volt);
+ return -EINVAL;
+ }
+
+ rc = of_property_read_u32(dev->of_node, "qcom,ldo-headroom-voltage",
+ &kvreg->headroom_volt);
+ if (rc < 0) {
+ dev_err(dev, "qcom,ldo-headroom-voltage missing rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(dev->of_node, "qcom,ldo-config-init",
+ &kvreg->ldo_config_init);
+ if (rc < 0) {
+ dev_err(dev, "qcom,ldo-config-init missing rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(dev->of_node, "qcom,apm-config-init",
+ &kvreg->apm_config_init);
+ if (rc < 0) {
+ dev_err(dev, "qcom,apm-config-init missing rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(dev->of_node, "qcom,cluster-num",
+ &kvreg->cluster_num);
+ if (rc < 0) {
+ dev_err(dev, "qcom,cluster-num missing rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int kryo_regulator_retention_init(struct kryo_regulator *kvreg,
+ struct platform_device *pdev,
+ struct device_node *ret_node)
+{
+ struct device *dev = &pdev->dev;
+ struct regulator_init_data *init_data;
+ struct regulator_config reg_config = {};
+ int rc = 0;
+
+ init_data = of_get_regulator_init_data(dev, ret_node,
+ &kvreg->retention_desc);
+ if (!init_data) {
+ kvreg_err(kvreg, "regulator init data is missing\n");
+ return -EINVAL;
+ }
+
+ if (!init_data->constraints.name) {
+ kvreg_err(kvreg, "regulator name is missing from constraints\n");
+ return -EINVAL;
+ }
+
+ init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_BYPASS
+ | REGULATOR_CHANGE_VOLTAGE;
+ init_data->constraints.input_uV = init_data->constraints.max_uV;
+
+ kvreg->retention_desc.name = init_data->constraints.name;
+ kvreg->retention_desc.n_voltages = LDO_N_VOLTAGES;
+ kvreg->retention_desc.ops = &kryo_regulator_retention_ops;
+ kvreg->retention_desc.type = REGULATOR_VOLTAGE;
+ kvreg->retention_desc.owner = THIS_MODULE;
+
+ reg_config.dev = dev;
+ reg_config.init_data = init_data;
+ reg_config.driver_data = kvreg;
+ reg_config.of_node = ret_node;
+ kvreg->retention_rdev = regulator_register(&kvreg->retention_desc,
+ ®_config);
+ if (IS_ERR(kvreg->retention_rdev)) {
+ rc = PTR_ERR(kvreg->retention_rdev);
+ kvreg_err(kvreg, "regulator_register failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int kryo_regulator_lpm_prepare(struct kryo_regulator *kvreg)
+{
+ int vdd_volt_uv, bhs_volt, vdd_vlvl = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+
+ kvreg->pre_lpm_state_mode = kvreg->mode;
+ kvreg->pre_lpm_state_volt = kvreg->volt;
+
+ if (kvreg->mode == LDO_MODE) {
+ if (!vdd_vlvl) {
+ vdd_vlvl = msm_spm_get_vdd(SHARED_CPU_REG_NUM);
+ if (vdd_vlvl < 0) {
+ kvreg_err(kvreg, "could not get vdd supply voltage level, rc=%d\n",
+ vdd_vlvl);
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+ return NOTIFY_BAD;
+ }
+
+ vdd_volt_uv = vdd_vlvl * VDD_SUPPLY_STEP_UV
+ + VDD_SUPPLY_MIN_UV;
+ }
+ kvreg_debug(kvreg, "switching to BHS mode, vdd_apcc=%d uV, current LDO Vref=%d, LPM enter count=%lx\n",
+ vdd_volt_uv, kvreg->volt, kvreg->lpm_enter_count);
+
+ /*
+ * Program vdd supply minus LDO headroom as voltage.
+ * Cap this value to the maximum physically supported
+ * LDO voltage, if necessary.
+ */
+ bhs_volt = vdd_volt_uv - kvreg->headroom_volt;
+ if (bhs_volt > kvreg->vref_func_max_volt) {
+ kvreg_debug(kvreg, "limited to LDO output of %d uV when switching to BHS mode\n",
+ kvreg->vref_func_max_volt);
+ bhs_volt = kvreg->vref_func_max_volt;
+ }
+
+ kryo_set_ldo_volt(kvreg, bhs_volt);
+
+ /* Switch Power Gate Mode */
+ kryo_configure_mode(kvreg, BHS_MODE);
+ }
+
+ kvreg->lpm_enter_count++;
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ return NOTIFY_OK;
+}
+
+static int kryo_regulator_lpm_resume(struct kryo_regulator *kvreg)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&kvreg->slock, flags);
+
+ if (kvreg->mode == BHS_MODE &&
+ kvreg->pre_lpm_state_mode == LDO_MODE) {
+ kvreg_debug(kvreg, "switching to LDO mode, cached LDO Vref=%d, LPM exit count=%lx\n",
+ kvreg->pre_lpm_state_volt, kvreg->lpm_exit_count);
+
+ /*
+ * Cached voltage value corresponds to vdd supply minus
+ * LDO headroom, reprogram it.
+ */
+ kryo_set_ldo_volt(kvreg, kvreg->volt);
+
+ /* Switch Power Gate Mode */
+ kryo_configure_mode(kvreg, LDO_MODE);
+
+ /* Request final LDO output voltage */
+ kryo_set_ldo_volt(kvreg, kvreg->pre_lpm_state_volt);
+ }
+
+ kvreg->lpm_exit_count++;
+ spin_unlock_irqrestore(&kvreg->slock, flags);
+
+ if (kvreg->lpm_exit_count != kvreg->lpm_enter_count) {
+ kvreg_err(kvreg, "LPM entry/exit counter mismatch, this is not expected: enter=%lx exit=%lx\n",
+ kvreg->lpm_enter_count, kvreg->lpm_exit_count);
+ BUG_ON(1);
+ }
+
+ return NOTIFY_OK;
+}
+
+static int kryo_regulator_cpu_pm_callback(struct notifier_block *self,
+ unsigned long cmd, void *v)
+{
+ struct kryo_regulator *kvreg = container_of(self, struct kryo_regulator,
+ cpu_pm_notifier);
+ unsigned long aff_level = (unsigned long) v;
+ int rc = NOTIFY_OK;
+
+ switch (cmd) {
+ case CPU_CLUSTER_PM_ENTER:
+ if (aff_level == AFFINITY_LEVEL_M3)
+ rc = kryo_regulator_lpm_prepare(kvreg);
+ break;
+ case CPU_CLUSTER_PM_EXIT:
+ if (aff_level == AFFINITY_LEVEL_M3)
+ rc = kryo_regulator_lpm_resume(kvreg);
+ break;
+ }
+
+ return rc;
+}
+
+static int kryo_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct kryo_regulator *kvreg;
+ struct regulator_config reg_config = {};
+ struct regulator_init_data *init_data = pdev->dev.platform_data;
+ struct device_node *child;
+ int rc = 0;
+
+ if (!dev->of_node) {
+ dev_err(dev, "Device tree node is missing\n");
+ return -ENODEV;
+ }
+
+ init_data = of_get_regulator_init_data(dev, dev->of_node, NULL);
+
+ if (!init_data) {
+ dev_err(dev, "regulator init data is missing\n");
+ return -EINVAL;
+ }
+
+ if (!init_data->constraints.name) {
+ dev_err(dev, "regulator name is missing from constraints\n");
+ return -EINVAL;
+ }
+
+ init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE
+ | REGULATOR_CHANGE_BYPASS | REGULATOR_CHANGE_STATUS;
+ init_data->constraints.input_uV = init_data->constraints.max_uV;
+
+ kvreg = devm_kzalloc(dev, sizeof(*kvreg), GFP_KERNEL);
+ if (!kvreg)
+ return -ENOMEM;
+
+ rc = kryo_regulator_init_data(pdev, kvreg);
+ if (rc) {
+ dev_err(dev, "could not parse and ioremap all device tree properties\n");
+ return rc;
+ }
+
+ spin_lock_init(&kvreg->slock);
+ kvreg->name = init_data->constraints.name;
+ kvreg->desc.name = kvreg->name;
+ kvreg->desc.n_voltages = LDO_N_VOLTAGES;
+ kvreg->desc.ops = &kryo_regulator_ops;
+ kvreg->desc.type = REGULATOR_VOLTAGE;
+ kvreg->desc.owner = THIS_MODULE;
+ kvreg->mode = BHS_MODE;
+
+ for_each_available_child_of_node(dev->of_node, child) {
+ kryo_regulator_retention_init(kvreg, pdev, child);
+ if (rc) {
+ dev_err(dev, "could not initialize retention regulator, rc=%d\n",
+ rc);
+ return rc;
+ }
+ break;
+ }
+
+ /* CPUSS PM Register Initialization */
+ rc = kryo_hw_init(kvreg);
+ if (rc) {
+ dev_err(dev, "unable to perform CPUSS PM initialization sequence\n");
+ return rc;
+ }
+
+ reg_config.dev = dev;
+ reg_config.init_data = init_data;
+ reg_config.driver_data = kvreg;
+ reg_config.of_node = dev->of_node;
+ kvreg->rdev = regulator_register(&kvreg->desc, ®_config);
+ if (IS_ERR(kvreg->rdev)) {
+ rc = PTR_ERR(kvreg->rdev);
+ kvreg_err(kvreg, "regulator_register failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, kvreg);
+ kryo_debugfs_init(kvreg);
+
+ mutex_lock(&kryo_regulator_list_mutex);
+ list_add_tail(&kvreg->link, &kryo_regulator_list);
+ mutex_unlock(&kryo_regulator_list_mutex);
+
+ kvreg->cpu_pm_notifier.notifier_call = kryo_regulator_cpu_pm_callback;
+ cpu_pm_register_notifier(&kvreg->cpu_pm_notifier);
+ kvreg_debug(kvreg, "registered cpu pm notifier\n");
+
+ kvreg_info(kvreg, "default LDO functional volt=%d uV, LDO retention volt=%d uV, Vref func=%d + %d*(val), cluster-num=%d\n",
+ kvreg->volt, kvreg->retention_volt,
+ kvreg->vref_func_min_volt,
+ kvreg->vref_func_step_volt,
+ kvreg->cluster_num);
+
+ return rc;
+}
+
+static int kryo_regulator_remove(struct platform_device *pdev)
+{
+ struct kryo_regulator *kvreg = platform_get_drvdata(pdev);
+
+ mutex_lock(&kryo_regulator_list_mutex);
+ list_del(&kvreg->link);
+ mutex_unlock(&kryo_regulator_list_mutex);
+
+ cpu_pm_unregister_notifier(&kvreg->cpu_pm_notifier);
+ regulator_unregister(kvreg->rdev);
+ platform_set_drvdata(pdev, NULL);
+ kryo_debugfs_deinit(kvreg);
+
+ return 0;
+}
+
+static const struct of_device_id kryo_regulator_match_table[] = {
+ { .compatible = "qcom,kryo-regulator", },
+ {}
+};
+
+static struct platform_driver kryo_regulator_driver = {
+ .probe = kryo_regulator_probe,
+ .remove = kryo_regulator_remove,
+ .driver = {
+ .name = KRYO_REGULATOR_DRIVER_NAME,
+ .of_match_table = kryo_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init kryo_regulator_init(void)
+{
+ kryo_debugfs_base_init();
+ return platform_driver_register(&kryo_regulator_driver);
+}
+
+static void __exit kryo_regulator_exit(void)
+{
+ platform_driver_unregister(&kryo_regulator_driver);
+ kryo_debugfs_base_remove();
+}
+
+MODULE_DESCRIPTION("Kryo regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(kryo_regulator_init);
+module_exit(kryo_regulator_exit);
diff --git a/drivers/regulator/mem-acc-regulator.c b/drivers/regulator/mem-acc-regulator.c
new file mode 100644
index 0000000..4c03dec
--- /dev/null
+++ b/drivers/regulator/mem-acc-regulator.c
@@ -0,0 +1,1387 @@
+/* Copyright (c) 2014-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) "ACC: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/string.h>
+#include <soc/qcom/scm.h>
+
+#define MEM_ACC_DEFAULT_SEL_SIZE 2
+
+#define BYTES_PER_FUSE_ROW 8
+
+/* mem-acc config flags */
+
+enum {
+ MEM_ACC_USE_CORNER_ACC_MAP = BIT(0),
+ MEM_ACC_USE_ADDR_VAL_MAP = BIT(1),
+};
+
+#define FUSE_MAP_NO_MATCH (-1)
+#define FUSE_PARAM_MATCH_ANY (-1)
+#define PARAM_MATCH_ANY (-1)
+
+enum {
+ MEMORY_L1,
+ MEMORY_L2,
+ MEMORY_MAX,
+};
+
+#define MEM_ACC_TYPE_MAX 6
+
+/**
+ * struct acc_reg_value - Acc register configuration structure
+ * @addr_index: An index in to phys_reg_addr_list and remap_reg_addr_list
+ * to get the ACC register physical address and remapped address.
+ * @reg_val: Value to program in to the register mapped by addr_index.
+ */
+struct acc_reg_value {
+ u32 addr_index;
+ u32 reg_val;
+};
+
+struct corner_acc_reg_config {
+ struct acc_reg_value *reg_config_list;
+ int max_reg_config_len;
+};
+
+struct mem_acc_regulator {
+ struct device *dev;
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+
+ int corner;
+ bool mem_acc_supported[MEMORY_MAX];
+ bool mem_acc_custom_supported[MEMORY_MAX];
+
+ u32 *acc_sel_mask[MEMORY_MAX];
+ u32 *acc_sel_bit_pos[MEMORY_MAX];
+ u32 acc_sel_bit_size[MEMORY_MAX];
+ u32 num_acc_sel[MEMORY_MAX];
+ u32 *acc_en_bit_pos;
+ u32 num_acc_en;
+ u32 *corner_acc_map;
+ u32 num_corners;
+ u32 override_fuse_value;
+ int override_map_match;
+ int override_map_count;
+
+
+ void __iomem *acc_sel_base[MEMORY_MAX];
+ void __iomem *acc_en_base;
+ phys_addr_t acc_sel_addr[MEMORY_MAX];
+ phys_addr_t acc_en_addr;
+ u32 flags;
+
+ void __iomem *acc_custom_addr[MEMORY_MAX];
+ u32 *acc_custom_data[MEMORY_MAX];
+
+ phys_addr_t mem_acc_type_addr[MEM_ACC_TYPE_MAX];
+ u32 *mem_acc_type_data;
+
+ /* eFuse parameters */
+ phys_addr_t efuse_addr;
+ void __iomem *efuse_base;
+
+ u32 num_acc_reg;
+ u32 *phys_reg_addr_list;
+ void __iomem **remap_reg_addr_list;
+ struct corner_acc_reg_config *corner_acc_reg_config;
+};
+
+static DEFINE_MUTEX(mem_acc_memory_mutex);
+
+static u64 mem_acc_read_efuse_row(struct mem_acc_regulator *mem_acc_vreg,
+ u32 row_num, bool use_tz_api)
+{
+ int rc;
+ u64 efuse_bits;
+ struct scm_desc desc = {0};
+ struct mem_acc_read_req {
+ u32 row_address;
+ int addr_type;
+ } req;
+
+ struct mem_acc_read_rsp {
+ u32 row_data[2];
+ u32 status;
+ } rsp;
+
+ if (!use_tz_api) {
+ efuse_bits = readq_relaxed(mem_acc_vreg->efuse_base
+ + row_num * BYTES_PER_FUSE_ROW);
+ return efuse_bits;
+ }
+
+ desc.args[0] = req.row_address = mem_acc_vreg->efuse_addr +
+ row_num * BYTES_PER_FUSE_ROW;
+ desc.args[1] = req.addr_type = 0;
+ desc.arginfo = SCM_ARGS(2);
+ efuse_bits = 0;
+
+ if (!is_scm_armv8()) {
+ rc = scm_call(SCM_SVC_FUSE, SCM_FUSE_READ,
+ &req, sizeof(req), &rsp, sizeof(rsp));
+ } else {
+ rc = scm_call2(SCM_SIP_FNID(SCM_SVC_FUSE, SCM_FUSE_READ),
+ &desc);
+ rsp.row_data[0] = desc.ret[0];
+ rsp.row_data[1] = desc.ret[1];
+ rsp.status = desc.ret[2];
+ }
+
+ if (rc) {
+ pr_err("read row %d failed, err code = %d", row_num, rc);
+ } else {
+ efuse_bits = ((u64)(rsp.row_data[1]) << 32) +
+ (u64)rsp.row_data[0];
+ }
+
+ return efuse_bits;
+}
+
+static inline u32 apc_to_acc_corner(struct mem_acc_regulator *mem_acc_vreg,
+ int corner)
+{
+ /*
+ * corner_acc_map maps the corner from index 0 and APC corner value
+ * starts from the value 1
+ */
+ return mem_acc_vreg->corner_acc_map[corner - 1];
+}
+
+static void __update_acc_sel(struct mem_acc_regulator *mem_acc_vreg,
+ int corner, int mem_type)
+{
+ u32 acc_data, acc_data_old, i, bit, acc_corner;
+
+ acc_data = readl_relaxed(mem_acc_vreg->acc_sel_base[mem_type]);
+ acc_data_old = acc_data;
+ for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
+ bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
+ acc_data &= ~mem_acc_vreg->acc_sel_mask[mem_type][i];
+ acc_corner = apc_to_acc_corner(mem_acc_vreg, corner);
+ acc_data |= (acc_corner << bit) &
+ mem_acc_vreg->acc_sel_mask[mem_type][i];
+ }
+ pr_debug("corner=%d old_acc_sel=0x%02x new_acc_sel=0x%02x mem_type=%d\n",
+ corner, acc_data_old, acc_data, mem_type);
+ writel_relaxed(acc_data, mem_acc_vreg->acc_sel_base[mem_type]);
+}
+
+static void __update_acc_type(struct mem_acc_regulator *mem_acc_vreg,
+ int corner)
+{
+ int i, rc;
+
+ for (i = 0; i < MEM_ACC_TYPE_MAX; i++) {
+ if (mem_acc_vreg->mem_acc_type_addr[i]) {
+ rc = scm_io_write(mem_acc_vreg->mem_acc_type_addr[i],
+ mem_acc_vreg->mem_acc_type_data[corner - 1 + i *
+ mem_acc_vreg->num_corners]);
+ if (rc)
+ pr_err("scm_io_write: %pa failure rc:%d\n",
+ &(mem_acc_vreg->mem_acc_type_addr[i]),
+ rc);
+ }
+ }
+}
+
+static void __update_acc_custom(struct mem_acc_regulator *mem_acc_vreg,
+ int corner, int mem_type)
+{
+ writel_relaxed(
+ mem_acc_vreg->acc_custom_data[mem_type][corner-1],
+ mem_acc_vreg->acc_custom_addr[mem_type]);
+ pr_debug("corner=%d mem_type=%d custom_data=0x%2x\n", corner,
+ mem_type, mem_acc_vreg->acc_custom_data[mem_type][corner-1]);
+}
+
+static void update_acc_sel(struct mem_acc_regulator *mem_acc_vreg, int corner)
+{
+ int i;
+
+ for (i = 0; i < MEMORY_MAX; i++) {
+ if (mem_acc_vreg->mem_acc_supported[i])
+ __update_acc_sel(mem_acc_vreg, corner, i);
+ if (mem_acc_vreg->mem_acc_custom_supported[i])
+ __update_acc_custom(mem_acc_vreg, corner, i);
+ }
+
+ if (mem_acc_vreg->mem_acc_type_data)
+ __update_acc_type(mem_acc_vreg, corner);
+}
+
+static void update_acc_reg(struct mem_acc_regulator *mem_acc_vreg, int corner)
+{
+ struct corner_acc_reg_config *corner_acc_reg_config;
+ struct acc_reg_value *reg_config_list;
+ int i, index;
+ u32 addr_index, reg_val;
+
+ corner_acc_reg_config =
+ &mem_acc_vreg->corner_acc_reg_config[mem_acc_vreg->corner];
+ reg_config_list = corner_acc_reg_config->reg_config_list;
+ for (i = 0; i < corner_acc_reg_config->max_reg_config_len; i++) {
+ /*
+ * Use (corner - 1) in the below equation as
+ * the reg_config_list[] stores the values starting from
+ * index '0' where as the minimum corner value allowed
+ * in regulator framework is '1'.
+ */
+ index = (corner - 1) * corner_acc_reg_config->max_reg_config_len
+ + i;
+ addr_index = reg_config_list[index].addr_index;
+ reg_val = reg_config_list[index].reg_val;
+
+ if (addr_index == PARAM_MATCH_ANY)
+ break;
+
+ writel_relaxed(reg_val,
+ mem_acc_vreg->remap_reg_addr_list[addr_index]);
+ /* make sure write complete */
+ mb();
+
+ pr_debug("corner=%d register:0x%x value:0x%x\n", corner,
+ mem_acc_vreg->phys_reg_addr_list[addr_index], reg_val);
+ }
+}
+
+static int mem_acc_regulator_set_voltage(struct regulator_dev *rdev,
+ int corner, int corner_max, unsigned int *selector)
+{
+ struct mem_acc_regulator *mem_acc_vreg = rdev_get_drvdata(rdev);
+ int i;
+
+ if (corner > mem_acc_vreg->num_corners) {
+ pr_err("Invalid corner=%d requested\n", corner);
+ return -EINVAL;
+ }
+
+ pr_debug("old corner=%d, new corner=%d\n",
+ mem_acc_vreg->corner, corner);
+
+ if (corner == mem_acc_vreg->corner)
+ return 0;
+
+ /* go up or down one level at a time */
+ mutex_lock(&mem_acc_memory_mutex);
+
+ if (mem_acc_vreg->flags & MEM_ACC_USE_ADDR_VAL_MAP) {
+ update_acc_reg(mem_acc_vreg, corner);
+ } else if (mem_acc_vreg->flags & MEM_ACC_USE_CORNER_ACC_MAP) {
+ if (corner > mem_acc_vreg->corner) {
+ for (i = mem_acc_vreg->corner + 1; i <= corner; i++) {
+ pr_debug("UP: to corner %d\n", i);
+ update_acc_sel(mem_acc_vreg, i);
+ }
+ } else {
+ for (i = mem_acc_vreg->corner - 1; i >= corner; i--) {
+ pr_debug("DOWN: to corner %d\n", i);
+ update_acc_sel(mem_acc_vreg, i);
+ }
+ }
+ }
+
+ mutex_unlock(&mem_acc_memory_mutex);
+
+ pr_debug("new voltage corner set %d\n", corner);
+
+ mem_acc_vreg->corner = corner;
+
+ return 0;
+}
+
+static int mem_acc_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct mem_acc_regulator *mem_acc_vreg = rdev_get_drvdata(rdev);
+
+ return mem_acc_vreg->corner;
+}
+
+static struct regulator_ops mem_acc_corner_ops = {
+ .set_voltage = mem_acc_regulator_set_voltage,
+ .get_voltage = mem_acc_regulator_get_voltage,
+};
+
+static int __mem_acc_sel_init(struct mem_acc_regulator *mem_acc_vreg,
+ int mem_type)
+{
+ int i;
+ u32 bit, mask;
+
+ mem_acc_vreg->acc_sel_mask[mem_type] = devm_kzalloc(mem_acc_vreg->dev,
+ mem_acc_vreg->num_acc_sel[mem_type] * sizeof(u32), GFP_KERNEL);
+ if (!mem_acc_vreg->acc_sel_mask[mem_type])
+ return -ENOMEM;
+
+ for (i = 0; i < mem_acc_vreg->num_acc_sel[mem_type]; i++) {
+ bit = mem_acc_vreg->acc_sel_bit_pos[mem_type][i];
+ mask = BIT(mem_acc_vreg->acc_sel_bit_size[mem_type]) - 1;
+ mem_acc_vreg->acc_sel_mask[mem_type][i] = mask << bit;
+ }
+
+ return 0;
+}
+
+static int mem_acc_sel_init(struct mem_acc_regulator *mem_acc_vreg)
+{
+ int i, rc;
+
+ for (i = 0; i < MEMORY_MAX; i++) {
+ if (mem_acc_vreg->mem_acc_supported[i]) {
+ rc = __mem_acc_sel_init(mem_acc_vreg, i);
+ if (rc) {
+ pr_err("Unable to initialize mem_type=%d rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void mem_acc_en_init(struct mem_acc_regulator *mem_acc_vreg)
+{
+ int i, bit;
+ u32 acc_data;
+
+ acc_data = readl_relaxed(mem_acc_vreg->acc_en_base);
+ pr_debug("init: acc_en_register=%x\n", acc_data);
+ for (i = 0; i < mem_acc_vreg->num_acc_en; i++) {
+ bit = mem_acc_vreg->acc_en_bit_pos[i];
+ acc_data |= BIT(bit);
+ }
+ pr_debug("final: acc_en_register=%x\n", acc_data);
+ writel_relaxed(acc_data, mem_acc_vreg->acc_en_base);
+}
+
+static int populate_acc_data(struct mem_acc_regulator *mem_acc_vreg,
+ const char *prop_name, u32 **value, u32 *len)
+{
+ int rc;
+
+ if (!of_get_property(mem_acc_vreg->dev->of_node, prop_name, len)) {
+ pr_err("Unable to find %s property\n", prop_name);
+ return -EINVAL;
+ }
+ *len /= sizeof(u32);
+ if (!(*len)) {
+ pr_err("Incorrect entries in %s\n", prop_name);
+ return -EINVAL;
+ }
+
+ *value = devm_kzalloc(mem_acc_vreg->dev, (*len) * sizeof(u32),
+ GFP_KERNEL);
+ if (!(*value)) {
+ pr_err("Unable to allocate memory for %s\n", prop_name);
+ return -ENOMEM;
+ }
+
+ pr_debug("Found %s, data-length = %d\n", prop_name, *len);
+
+ rc = of_property_read_u32_array(mem_acc_vreg->dev->of_node,
+ prop_name, *value, *len);
+ if (rc) {
+ pr_err("Unable to populate %s rc=%d\n", prop_name, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int mem_acc_sel_setup(struct mem_acc_regulator *mem_acc_vreg,
+ struct resource *res, int mem_type)
+{
+ int len, rc;
+ char *mem_select_str;
+ char *mem_select_size_str;
+
+ mem_acc_vreg->acc_sel_addr[mem_type] = res->start;
+ len = res->end - res->start + 1;
+ pr_debug("'acc_sel_addr' = %pa mem_type=%d (len=%d)\n",
+ &res->start, mem_type, len);
+
+ mem_acc_vreg->acc_sel_base[mem_type] = devm_ioremap(mem_acc_vreg->dev,
+ mem_acc_vreg->acc_sel_addr[mem_type], len);
+ if (!mem_acc_vreg->acc_sel_base[mem_type]) {
+ pr_err("Unable to map 'acc_sel_addr' %pa for mem_type=%d\n",
+ &mem_acc_vreg->acc_sel_addr[mem_type], mem_type);
+ return -EINVAL;
+ }
+
+ switch (mem_type) {
+ case MEMORY_L1:
+ mem_select_str = "qcom,acc-sel-l1-bit-pos";
+ mem_select_size_str = "qcom,acc-sel-l1-bit-size";
+ break;
+ case MEMORY_L2:
+ mem_select_str = "qcom,acc-sel-l2-bit-pos";
+ mem_select_size_str = "qcom,acc-sel-l2-bit-size";
+ break;
+ default:
+ pr_err("Invalid memory type: %d\n", mem_type);
+ return -EINVAL;
+ }
+
+ mem_acc_vreg->acc_sel_bit_size[mem_type] = MEM_ACC_DEFAULT_SEL_SIZE;
+ of_property_read_u32(mem_acc_vreg->dev->of_node, mem_select_size_str,
+ &mem_acc_vreg->acc_sel_bit_size[mem_type]);
+
+ rc = populate_acc_data(mem_acc_vreg, mem_select_str,
+ &mem_acc_vreg->acc_sel_bit_pos[mem_type],
+ &mem_acc_vreg->num_acc_sel[mem_type]);
+ if (rc)
+ pr_err("Unable to populate '%s' rc=%d\n", mem_select_str, rc);
+
+ return rc;
+}
+
+static int mem_acc_efuse_init(struct platform_device *pdev,
+ struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct resource *res;
+ int len;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr");
+ if (!res || !res->start) {
+ mem_acc_vreg->efuse_base = NULL;
+ pr_debug("'efuse_addr' resource missing or not used.\n");
+ return 0;
+ }
+
+ mem_acc_vreg->efuse_addr = res->start;
+ len = res->end - res->start + 1;
+
+ pr_info("efuse_addr = %pa (len=0x%x)\n", &res->start, len);
+
+ mem_acc_vreg->efuse_base = devm_ioremap(&pdev->dev,
+ mem_acc_vreg->efuse_addr, len);
+ if (!mem_acc_vreg->efuse_base) {
+ pr_err("Unable to map efuse_addr %pa\n",
+ &mem_acc_vreg->efuse_addr);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mem_acc_custom_data_init(struct platform_device *pdev,
+ struct mem_acc_regulator *mem_acc_vreg,
+ int mem_type)
+{
+ struct resource *res;
+ char *custom_apc_addr_str, *custom_apc_data_str;
+ int len, rc = 0;
+
+ switch (mem_type) {
+ case MEMORY_L1:
+ custom_apc_addr_str = "acc-l1-custom";
+ custom_apc_data_str = "qcom,l1-acc-custom-data";
+ break;
+ case MEMORY_L2:
+ custom_apc_addr_str = "acc-l2-custom";
+ custom_apc_data_str = "qcom,l2-acc-custom-data";
+ break;
+ default:
+ pr_err("Invalid memory type: %d\n", mem_type);
+ return -EINVAL;
+ }
+
+ if (!of_find_property(mem_acc_vreg->dev->of_node,
+ custom_apc_data_str, NULL)) {
+ pr_debug("%s custom_data not specified\n", custom_apc_data_str);
+ return 0;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ custom_apc_addr_str);
+ if (!res || !res->start) {
+ pr_debug("%s resource missing\n", custom_apc_addr_str);
+ return -EINVAL;
+ }
+
+ len = res->end - res->start + 1;
+ mem_acc_vreg->acc_custom_addr[mem_type] =
+ devm_ioremap(mem_acc_vreg->dev, res->start, len);
+ if (!mem_acc_vreg->acc_custom_addr[mem_type]) {
+ pr_err("Unable to map %s %pa\n",
+ custom_apc_addr_str, &res->start);
+ return -EINVAL;
+ }
+
+ rc = populate_acc_data(mem_acc_vreg, custom_apc_data_str,
+ &mem_acc_vreg->acc_custom_data[mem_type], &len);
+ if (rc) {
+ pr_err("Unable to find %s rc=%d\n", custom_apc_data_str, rc);
+ return rc;
+ }
+
+ if (mem_acc_vreg->num_corners != len) {
+ pr_err("Custom data is not present for all the corners\n");
+ return -EINVAL;
+ }
+
+ mem_acc_vreg->mem_acc_custom_supported[mem_type] = true;
+
+ return 0;
+}
+
+static int override_mem_acc_custom_data(struct platform_device *pdev,
+ struct mem_acc_regulator *mem_acc_vreg,
+ int mem_type)
+{
+ char *custom_apc_data_str;
+ int len, rc = 0, i;
+ int tuple_count, tuple_match;
+ u32 index = 0, value = 0;
+
+ switch (mem_type) {
+ case MEMORY_L1:
+ custom_apc_data_str = "qcom,override-l1-acc-custom-data";
+ break;
+ case MEMORY_L2:
+ custom_apc_data_str = "qcom,override-l2-acc-custom-data";
+ break;
+ default:
+ pr_err("Invalid memory type: %d\n", mem_type);
+ return -EINVAL;
+ }
+
+ if (!of_find_property(mem_acc_vreg->dev->of_node,
+ custom_apc_data_str, &len)) {
+ pr_debug("%s not specified\n", custom_apc_data_str);
+ return 0;
+ }
+
+ if (mem_acc_vreg->override_map_count) {
+ if (mem_acc_vreg->override_map_match == FUSE_MAP_NO_MATCH)
+ return 0;
+ tuple_count = mem_acc_vreg->override_map_count;
+ tuple_match = mem_acc_vreg->override_map_match;
+ } else {
+ tuple_count = 1;
+ tuple_match = 0;
+ }
+
+ if (len != mem_acc_vreg->num_corners * tuple_count * sizeof(u32)) {
+ pr_err("%s length=%d is invalid\n", custom_apc_data_str, len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mem_acc_vreg->num_corners; i++) {
+ index = (tuple_match * mem_acc_vreg->num_corners) + i;
+ rc = of_property_read_u32_index(mem_acc_vreg->dev->of_node,
+ custom_apc_data_str, index, &value);
+ if (rc) {
+ pr_err("Unable read %s index %u, rc=%d\n",
+ custom_apc_data_str, index, rc);
+ return rc;
+ }
+ mem_acc_vreg->acc_custom_data[mem_type][i] = value;
+ }
+
+ return 0;
+}
+
+static int mem_acc_override_corner_map(struct mem_acc_regulator *mem_acc_vreg)
+{
+ int len = 0, i, rc;
+ int tuple_count, tuple_match;
+ u32 index = 0, value = 0;
+ char *prop_str = "qcom,override-corner-acc-map";
+
+ if (!of_find_property(mem_acc_vreg->dev->of_node, prop_str, &len))
+ return 0;
+
+ if (mem_acc_vreg->override_map_count) {
+ if (mem_acc_vreg->override_map_match == FUSE_MAP_NO_MATCH)
+ return 0;
+ tuple_count = mem_acc_vreg->override_map_count;
+ tuple_match = mem_acc_vreg->override_map_match;
+ } else {
+ tuple_count = 1;
+ tuple_match = 0;
+ }
+
+ if (len != mem_acc_vreg->num_corners * tuple_count * sizeof(u32)) {
+ pr_err("%s length=%d is invalid\n", prop_str, len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mem_acc_vreg->num_corners; i++) {
+ index = (tuple_match * mem_acc_vreg->num_corners) + i;
+ rc = of_property_read_u32_index(mem_acc_vreg->dev->of_node,
+ prop_str, index, &value);
+ if (rc) {
+ pr_err("Unable read %s index %u, rc=%d\n",
+ prop_str, index, rc);
+ return rc;
+ }
+ mem_acc_vreg->corner_acc_map[i] = value;
+ }
+
+ return 0;
+
+}
+
+static int mem_acc_find_override_map_match(struct platform_device *pdev,
+ struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct device_node *of_node = pdev->dev.of_node;
+ int i, rc, tuple_size;
+ int len = 0;
+ u32 *tmp;
+ char *prop_str = "qcom,override-fuse-version-map";
+
+ /* Specify default no match case. */
+ mem_acc_vreg->override_map_match = FUSE_MAP_NO_MATCH;
+ mem_acc_vreg->override_map_count = 0;
+
+ if (!of_find_property(of_node, prop_str, &len)) {
+ /* No mapping present. */
+ return 0;
+ }
+
+ tuple_size = 1;
+ mem_acc_vreg->override_map_count = len / (sizeof(u32) * tuple_size);
+
+ if (len == 0 || len % (sizeof(u32) * tuple_size)) {
+ pr_err("%s length=%d is invalid\n", prop_str, len);
+ return -EINVAL;
+ }
+
+ tmp = kzalloc(len, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, prop_str, tmp,
+ mem_acc_vreg->override_map_count * tuple_size);
+ if (rc) {
+ pr_err("could not read %s rc=%d\n", prop_str, rc);
+ goto done;
+ }
+
+ for (i = 0; i < mem_acc_vreg->override_map_count; i++) {
+ if (tmp[i * tuple_size] != mem_acc_vreg->override_fuse_value
+ && tmp[i * tuple_size] != FUSE_PARAM_MATCH_ANY) {
+ continue;
+ } else {
+ mem_acc_vreg->override_map_match = i;
+ break;
+ }
+ }
+
+ if (mem_acc_vreg->override_map_match != FUSE_MAP_NO_MATCH)
+ pr_debug("%s tuple match found: %d\n", prop_str,
+ mem_acc_vreg->override_map_match);
+ else
+ pr_err("%s tuple match not found\n", prop_str);
+
+done:
+ kfree(tmp);
+ return rc;
+}
+
+#define MAX_CHARS_PER_INT 20
+
+static int mem_acc_reg_addr_val_dump(struct mem_acc_regulator *mem_acc_vreg,
+ struct corner_acc_reg_config *corner_acc_reg_config,
+ u32 corner)
+{
+ int i, k, index, pos = 0;
+ u32 addr_index;
+ size_t buflen;
+ char *buf;
+ struct acc_reg_value *reg_config_list =
+ corner_acc_reg_config->reg_config_list;
+ int max_reg_config_len = corner_acc_reg_config->max_reg_config_len;
+ int num_corners = mem_acc_vreg->num_corners;
+
+ /*
+ * Log register and value mapping since they are useful for
+ * baseline MEM ACC logging.
+ */
+ buflen = max_reg_config_len * (MAX_CHARS_PER_INT + 6) * sizeof(*buf);
+ buf = kzalloc(buflen, GFP_KERNEL);
+ if (buf == NULL) {
+ pr_err("Could not allocate memory for acc register and value logging\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < num_corners; i++) {
+ if (corner == i + 1)
+ continue;
+
+ pr_debug("Corner: %d --> %d:\n", corner, i + 1);
+ pos = 0;
+ for (k = 0; k < max_reg_config_len; k++) {
+ index = i * max_reg_config_len + k;
+ addr_index = reg_config_list[index].addr_index;
+ if (addr_index == PARAM_MATCH_ANY)
+ break;
+
+ pos += scnprintf(buf + pos, buflen - pos,
+ "<0x%x 0x%x> ",
+ mem_acc_vreg->phys_reg_addr_list[addr_index],
+ reg_config_list[index].reg_val);
+ }
+ buf[pos] = '\0';
+ pr_debug("%s\n", buf);
+ }
+
+ kfree(buf);
+ return 0;
+}
+
+static int mem_acc_get_reg_addr_val(struct device_node *of_node,
+ const char *prop_str, struct acc_reg_value *reg_config_list,
+ int list_offset, int list_size, u32 max_reg_index)
+{
+
+ int i, index, rc = 0;
+
+ for (i = 0; i < list_size / 2; i++) {
+ index = (list_offset * list_size) + i * 2;
+ rc = of_property_read_u32_index(of_node, prop_str, index,
+ ®_config_list[i].addr_index);
+ rc |= of_property_read_u32_index(of_node, prop_str, index + 1,
+ ®_config_list[i].reg_val);
+ if (rc) {
+ pr_err("could not read %s at tuple %u: rc=%d\n",
+ prop_str, index, rc);
+ return rc;
+ }
+
+ if (reg_config_list[i].addr_index == PARAM_MATCH_ANY)
+ continue;
+
+ if ((!reg_config_list[i].addr_index) ||
+ reg_config_list[i].addr_index > max_reg_index) {
+ pr_err("Invalid register index %u in %s at tuple %u\n",
+ reg_config_list[i].addr_index, prop_str, index);
+ return -EINVAL;
+ }
+ }
+
+ return rc;
+}
+
+static int mem_acc_init_reg_config(struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct device_node *of_node = mem_acc_vreg->dev->of_node;
+ int i, size, len = 0, rc = 0;
+ u32 addr_index, reg_val, index;
+ char *prop_str = "qcom,acc-init-reg-config";
+
+ if (!of_find_property(of_node, prop_str, &len)) {
+ /* Initial acc register configuration not specified */
+ return rc;
+ }
+
+ size = len / sizeof(u32);
+ if ((!size) || (size % 2)) {
+ pr_err("%s specified with invalid length: %d\n",
+ prop_str, size);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size / 2; i++) {
+ index = i * 2;
+ rc = of_property_read_u32_index(of_node, prop_str, index,
+ &addr_index);
+ rc |= of_property_read_u32_index(of_node, prop_str, index + 1,
+ ®_val);
+ if (rc) {
+ pr_err("could not read %s at tuple %u: rc=%d\n",
+ prop_str, index, rc);
+ return rc;
+ }
+
+ if ((!addr_index) || addr_index > mem_acc_vreg->num_acc_reg) {
+ pr_err("Invalid register index %u in %s at tuple %u\n",
+ addr_index, prop_str, index);
+ return -EINVAL;
+ }
+
+ writel_relaxed(reg_val,
+ mem_acc_vreg->remap_reg_addr_list[addr_index]);
+ /* make sure write complete */
+ mb();
+
+ pr_debug("acc initial config: register:0x%x value:0x%x\n",
+ mem_acc_vreg->phys_reg_addr_list[addr_index], reg_val);
+ }
+
+ return rc;
+}
+
+static int mem_acc_get_reg_addr(struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct device_node *of_node = mem_acc_vreg->dev->of_node;
+ void __iomem **remap_reg_addr_list;
+ u32 *phys_reg_addr_list;
+ int i, num_acc_reg, len = 0, rc = 0;
+
+ if (!of_find_property(of_node, "qcom,acc-reg-addr-list", &len)) {
+ /* acc register address list not specified */
+ return rc;
+ }
+
+ num_acc_reg = len / sizeof(u32);
+ if (!num_acc_reg) {
+ pr_err("qcom,acc-reg-addr-list has invalid len = %d\n", len);
+ return -EINVAL;
+ }
+
+ phys_reg_addr_list = devm_kcalloc(mem_acc_vreg->dev, num_acc_reg + 1,
+ sizeof(*phys_reg_addr_list), GFP_KERNEL);
+ if (!phys_reg_addr_list)
+ return -ENOMEM;
+
+ remap_reg_addr_list = devm_kcalloc(mem_acc_vreg->dev, num_acc_reg + 1,
+ sizeof(*remap_reg_addr_list), GFP_KERNEL);
+ if (!remap_reg_addr_list)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, "qcom,acc-reg-addr-list",
+ &phys_reg_addr_list[1], num_acc_reg);
+ if (rc) {
+ pr_err("Read- qcom,acc-reg-addr-list failed: rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = 1; i <= num_acc_reg; i++) {
+ remap_reg_addr_list[i] = devm_ioremap(mem_acc_vreg->dev,
+ phys_reg_addr_list[i], 0x4);
+ if (!remap_reg_addr_list[i]) {
+ pr_err("Unable to map register address 0x%x\n",
+ phys_reg_addr_list[i]);
+ return -EINVAL;
+ }
+ }
+
+ mem_acc_vreg->num_acc_reg = num_acc_reg;
+ mem_acc_vreg->phys_reg_addr_list = phys_reg_addr_list;
+ mem_acc_vreg->remap_reg_addr_list = remap_reg_addr_list;
+
+ return rc;
+}
+
+static int mem_acc_reg_config_init(struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct device_node *of_node = mem_acc_vreg->dev->of_node;
+ struct acc_reg_value *reg_config_list;
+ int len, size, rc, i, num_corners;
+ struct property *prop;
+ char prop_str[30];
+ struct corner_acc_reg_config *corner_acc_reg_config;
+
+ rc = of_property_read_u32(of_node, "qcom,num-acc-corners",
+ &num_corners);
+ if (rc) {
+ pr_err("could not read qcom,num-acc-corners: rc=%d\n", rc);
+ return rc;
+ }
+
+ mem_acc_vreg->num_corners = num_corners;
+
+ rc = of_property_read_u32(of_node, "qcom,boot-acc-corner",
+ &mem_acc_vreg->corner);
+ if (rc) {
+ pr_err("could not read qcom,boot-acc-corner: rc=%d\n", rc);
+ return rc;
+ }
+ pr_debug("boot acc corner = %d\n", mem_acc_vreg->corner);
+
+ corner_acc_reg_config = devm_kcalloc(mem_acc_vreg->dev, num_corners + 1,
+ sizeof(*corner_acc_reg_config),
+ GFP_KERNEL);
+ if (!corner_acc_reg_config)
+ return -ENOMEM;
+
+ for (i = 1; i <= num_corners; i++) {
+ snprintf(prop_str, sizeof(prop_str),
+ "qcom,corner%d-reg-config", i);
+ prop = of_find_property(of_node, prop_str, &len);
+ size = len / sizeof(u32);
+ if ((!prop) || (!size) || size < (num_corners * 2)) {
+ pr_err("%s property is missed or invalid length: len=%d\n",
+ prop_str, len);
+ return -EINVAL;
+ }
+
+ reg_config_list = devm_kcalloc(mem_acc_vreg->dev, size / 2,
+ sizeof(*reg_config_list), GFP_KERNEL);
+ if (!reg_config_list)
+ return -ENOMEM;
+
+ rc = mem_acc_get_reg_addr_val(of_node, prop_str,
+ reg_config_list, 0, size,
+ mem_acc_vreg->num_acc_reg);
+ if (rc) {
+ pr_err("Failed to read %s property: rc=%d\n",
+ prop_str, rc);
+ return rc;
+ }
+
+ corner_acc_reg_config[i].max_reg_config_len =
+ size / (num_corners * 2);
+ corner_acc_reg_config[i].reg_config_list = reg_config_list;
+
+ rc = mem_acc_reg_addr_val_dump(mem_acc_vreg,
+ &corner_acc_reg_config[i], i);
+ if (rc) {
+ pr_err("could not dump acc address-value dump for corner=%d: rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+
+ mem_acc_vreg->corner_acc_reg_config = corner_acc_reg_config;
+ mem_acc_vreg->flags |= MEM_ACC_USE_ADDR_VAL_MAP;
+ return rc;
+}
+
+static int mem_acc_override_reg_addr_val_init(
+ struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct device_node *of_node = mem_acc_vreg->dev->of_node;
+ struct corner_acc_reg_config *corner_acc_reg_config;
+ struct acc_reg_value *override_reg_config_list;
+ int i, tuple_count, tuple_match, len = 0, rc = 0;
+ u32 list_size, override_max_reg_config_len;
+ char prop_str[40];
+ struct property *prop;
+ int num_corners = mem_acc_vreg->num_corners;
+
+ if (!mem_acc_vreg->corner_acc_reg_config)
+ return 0;
+
+ if (mem_acc_vreg->override_map_count) {
+ if (mem_acc_vreg->override_map_match == FUSE_MAP_NO_MATCH)
+ return 0;
+ tuple_count = mem_acc_vreg->override_map_count;
+ tuple_match = mem_acc_vreg->override_map_match;
+ } else {
+ tuple_count = 1;
+ tuple_match = 0;
+ }
+
+ corner_acc_reg_config = mem_acc_vreg->corner_acc_reg_config;
+ for (i = 1; i <= num_corners; i++) {
+ snprintf(prop_str, sizeof(prop_str),
+ "qcom,override-corner%d-addr-val-map", i);
+ prop = of_find_property(of_node, prop_str, &len);
+ list_size = len / (tuple_count * sizeof(u32));
+ if (!prop) {
+ pr_debug("%s property not specified\n", prop_str);
+ continue;
+ }
+
+ if ((!list_size) || list_size < (num_corners * 2)) {
+ pr_err("qcom,override-corner%d-addr-val-map property is missed or invalid length: len=%d\n",
+ i, len);
+ return -EINVAL;
+ }
+
+ override_max_reg_config_len = list_size / (num_corners * 2);
+ override_reg_config_list =
+ corner_acc_reg_config[i].reg_config_list;
+
+ if (corner_acc_reg_config[i].max_reg_config_len
+ != override_max_reg_config_len) {
+ /* Free already allocate memory */
+ devm_kfree(mem_acc_vreg->dev, override_reg_config_list);
+
+ /* Allocated memory for new requirement */
+ override_reg_config_list =
+ devm_kcalloc(mem_acc_vreg->dev,
+ override_max_reg_config_len * num_corners,
+ sizeof(*override_reg_config_list), GFP_KERNEL);
+ if (!override_reg_config_list)
+ return -ENOMEM;
+
+ corner_acc_reg_config[i].max_reg_config_len =
+ override_max_reg_config_len;
+ corner_acc_reg_config[i].reg_config_list =
+ override_reg_config_list;
+ }
+
+ rc = mem_acc_get_reg_addr_val(of_node, prop_str,
+ override_reg_config_list, tuple_match,
+ list_size, mem_acc_vreg->num_acc_reg);
+ if (rc) {
+ pr_err("Failed to read %s property: rc=%d\n",
+ prop_str, rc);
+ return rc;
+ }
+
+ rc = mem_acc_reg_addr_val_dump(mem_acc_vreg,
+ &corner_acc_reg_config[i], i);
+ if (rc) {
+ pr_err("could not dump acc address-value dump for corner=%d: rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+#define MEM_TYPE_STRING_LEN 20
+static int mem_acc_init(struct platform_device *pdev,
+ struct mem_acc_regulator *mem_acc_vreg)
+{
+ struct device_node *of_node = pdev->dev.of_node;
+ struct resource *res;
+ int len, rc, i, j;
+ u32 fuse_sel[4];
+ u64 fuse_bits;
+ bool acc_type_present = false;
+ char tmps[MEM_TYPE_STRING_LEN];
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-en");
+ if (!res || !res->start) {
+ pr_debug("'acc-en' resource missing or not used.\n");
+ } else {
+ mem_acc_vreg->acc_en_addr = res->start;
+ len = res->end - res->start + 1;
+ pr_debug("'acc_en_addr' = %pa (len=0x%x)\n", &res->start, len);
+
+ mem_acc_vreg->acc_en_base = devm_ioremap(mem_acc_vreg->dev,
+ mem_acc_vreg->acc_en_addr, len);
+ if (!mem_acc_vreg->acc_en_base) {
+ pr_err("Unable to map 'acc_en_addr' %pa\n",
+ &mem_acc_vreg->acc_en_addr);
+ return -EINVAL;
+ }
+
+ rc = populate_acc_data(mem_acc_vreg, "qcom,acc-en-bit-pos",
+ &mem_acc_vreg->acc_en_bit_pos,
+ &mem_acc_vreg->num_acc_en);
+ if (rc) {
+ pr_err("Unable to populate 'qcom,acc-en-bit-pos' rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = mem_acc_efuse_init(pdev, mem_acc_vreg);
+ if (rc) {
+ pr_err("Wrong eFuse address specified: rc=%d\n", rc);
+ return rc;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l1");
+ if (!res || !res->start) {
+ pr_debug("'acc-sel-l1' resource missing or not used.\n");
+ } else {
+ rc = mem_acc_sel_setup(mem_acc_vreg, res, MEMORY_L1);
+ if (rc) {
+ pr_err("Unable to setup mem-acc for mem_type=%d rc=%d\n",
+ MEMORY_L1, rc);
+ return rc;
+ }
+ mem_acc_vreg->mem_acc_supported[MEMORY_L1] = true;
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "acc-sel-l2");
+ if (!res || !res->start) {
+ pr_debug("'acc-sel-l2' resource missing or not used.\n");
+ } else {
+ rc = mem_acc_sel_setup(mem_acc_vreg, res, MEMORY_L2);
+ if (rc) {
+ pr_err("Unable to setup mem-acc for mem_type=%d rc=%d\n",
+ MEMORY_L2, rc);
+ return rc;
+ }
+ mem_acc_vreg->mem_acc_supported[MEMORY_L2] = true;
+ }
+
+ for (i = 0; i < MEM_ACC_TYPE_MAX; i++) {
+ snprintf(tmps, MEM_TYPE_STRING_LEN, "mem-acc-type%d", i + 1);
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, tmps);
+
+ if (!res || !res->start) {
+ pr_debug("'%s' resource missing or not used.\n", tmps);
+ } else {
+ mem_acc_vreg->mem_acc_type_addr[i] = res->start;
+ acc_type_present = true;
+ }
+ }
+
+ rc = mem_acc_get_reg_addr(mem_acc_vreg);
+ if (rc) {
+ pr_err("Unable to get acc register addresses: rc=%d\n", rc);
+ return rc;
+ }
+
+ if (mem_acc_vreg->phys_reg_addr_list) {
+ rc = mem_acc_reg_config_init(mem_acc_vreg);
+ if (rc) {
+ pr_err("acc register address-value map failed: rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (of_find_property(of_node, "qcom,corner-acc-map", NULL)) {
+ rc = populate_acc_data(mem_acc_vreg, "qcom,corner-acc-map",
+ &mem_acc_vreg->corner_acc_map,
+ &mem_acc_vreg->num_corners);
+
+ /* Check if at least one valid mem-acc config. is specified */
+ for (i = 0; i < MEMORY_MAX; i++) {
+ if (mem_acc_vreg->mem_acc_supported[i])
+ break;
+ }
+ if (i == MEMORY_MAX && !acc_type_present) {
+ pr_err("No mem-acc configuration specified\n");
+ return -EINVAL;
+ }
+
+ mem_acc_vreg->flags |= MEM_ACC_USE_CORNER_ACC_MAP;
+ }
+
+ if ((mem_acc_vreg->flags & MEM_ACC_USE_CORNER_ACC_MAP) &&
+ (mem_acc_vreg->flags & MEM_ACC_USE_ADDR_VAL_MAP)) {
+ pr_err("Invalid configuration, both qcom,corner-acc-map and qcom,cornerX-addr-val-map specified\n");
+ return -EINVAL;
+ }
+
+ pr_debug("num_corners = %d\n", mem_acc_vreg->num_corners);
+
+ if (mem_acc_vreg->num_acc_en)
+ mem_acc_en_init(mem_acc_vreg);
+
+ if (mem_acc_vreg->phys_reg_addr_list) {
+ rc = mem_acc_init_reg_config(mem_acc_vreg);
+ if (rc) {
+ pr_err("acc initial register configuration failed: rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = mem_acc_sel_init(mem_acc_vreg);
+ if (rc) {
+ pr_err("Unable to initialize mem_acc_sel reg rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < MEMORY_MAX; i++) {
+ rc = mem_acc_custom_data_init(pdev, mem_acc_vreg, i);
+ if (rc) {
+ pr_err("Unable to initialize custom data for mem_type=%d rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+
+ if (of_find_property(mem_acc_vreg->dev->of_node,
+ "qcom,override-acc-fuse-sel", NULL)) {
+ rc = of_property_read_u32_array(mem_acc_vreg->dev->of_node,
+ "qcom,override-acc-fuse-sel", fuse_sel, 4);
+ if (rc < 0) {
+ pr_err("Read failed - qcom,override-acc-fuse-sel rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ fuse_bits = mem_acc_read_efuse_row(mem_acc_vreg, fuse_sel[0],
+ fuse_sel[3]);
+ /*
+ * fuse_sel[1] = LSB position in row (shift)
+ * fuse_sel[2] = num of bits (mask)
+ */
+ mem_acc_vreg->override_fuse_value = (fuse_bits >> fuse_sel[1]) &
+ ((1 << fuse_sel[2]) - 1);
+
+ rc = mem_acc_find_override_map_match(pdev, mem_acc_vreg);
+ if (rc) {
+ pr_err("Unable to find fuse map match rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_debug("override_fuse_val=%d override_map_match=%d\n",
+ mem_acc_vreg->override_fuse_value,
+ mem_acc_vreg->override_map_match);
+
+ rc = mem_acc_override_corner_map(mem_acc_vreg);
+ if (rc) {
+ pr_err("Unable to override corner map rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = mem_acc_override_reg_addr_val_init(mem_acc_vreg);
+ if (rc) {
+ pr_err("Unable to override reg_config_list init rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ for (i = 0; i < MEMORY_MAX; i++) {
+ rc = override_mem_acc_custom_data(pdev,
+ mem_acc_vreg, i);
+ if (rc) {
+ pr_err("Unable to override custom data for mem_type=%d rc=%d\n",
+ i, rc);
+ return rc;
+ }
+ }
+ }
+
+ if (acc_type_present) {
+ mem_acc_vreg->mem_acc_type_data = devm_kzalloc(
+ mem_acc_vreg->dev, mem_acc_vreg->num_corners *
+ MEM_ACC_TYPE_MAX * sizeof(u32), GFP_KERNEL);
+
+ if (!mem_acc_vreg->mem_acc_type_data) {
+ pr_err("Unable to allocate memory for mem_acc_type\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < MEM_ACC_TYPE_MAX; i++) {
+ if (mem_acc_vreg->mem_acc_type_addr[i]) {
+ snprintf(tmps, MEM_TYPE_STRING_LEN,
+ "qcom,mem-acc-type%d", i + 1);
+
+ j = i * mem_acc_vreg->num_corners;
+ rc = of_property_read_u32_array(
+ mem_acc_vreg->dev->of_node,
+ tmps,
+ &mem_acc_vreg->mem_acc_type_data[j],
+ mem_acc_vreg->num_corners);
+ if (rc) {
+ pr_err("Unable to get property %s rc=%d\n",
+ tmps, rc);
+ return rc;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mem_acc_regulator_probe(struct platform_device *pdev)
+{
+ struct regulator_config reg_config = {};
+ struct mem_acc_regulator *mem_acc_vreg;
+ struct regulator_desc *rdesc;
+ struct regulator_init_data *init_data;
+ int rc;
+
+ if (!pdev->dev.of_node) {
+ pr_err("Device tree node is missing\n");
+ return -EINVAL;
+ }
+
+ init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node,
+ NULL);
+ if (!init_data) {
+ pr_err("regulator init data is missing\n");
+ return -EINVAL;
+ }
+
+ init_data->constraints.input_uV = init_data->constraints.max_uV;
+ init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE;
+
+ mem_acc_vreg = devm_kzalloc(&pdev->dev, sizeof(*mem_acc_vreg),
+ GFP_KERNEL);
+ if (!mem_acc_vreg)
+ return -ENOMEM;
+
+ mem_acc_vreg->dev = &pdev->dev;
+
+ rc = mem_acc_init(pdev, mem_acc_vreg);
+ if (rc) {
+ pr_err("Unable to initialize mem_acc configuration rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rdesc = &mem_acc_vreg->rdesc;
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+ rdesc->ops = &mem_acc_corner_ops;
+ rdesc->name = init_data->constraints.name;
+
+ reg_config.dev = &pdev->dev;
+ reg_config.init_data = init_data;
+ reg_config.driver_data = mem_acc_vreg;
+ reg_config.of_node = pdev->dev.of_node;
+ mem_acc_vreg->rdev = regulator_register(rdesc, ®_config);
+ if (IS_ERR(mem_acc_vreg->rdev)) {
+ rc = PTR_ERR(mem_acc_vreg->rdev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("regulator_register failed: rc=%d\n", rc);
+ return rc;
+ }
+
+ platform_set_drvdata(pdev, mem_acc_vreg);
+
+ return 0;
+}
+
+static int mem_acc_regulator_remove(struct platform_device *pdev)
+{
+ struct mem_acc_regulator *mem_acc_vreg = platform_get_drvdata(pdev);
+
+ regulator_unregister(mem_acc_vreg->rdev);
+
+ return 0;
+}
+
+static const struct of_device_id mem_acc_regulator_match_table[] = {
+ { .compatible = "qcom,mem-acc-regulator", },
+ {}
+};
+
+static struct platform_driver mem_acc_regulator_driver = {
+ .probe = mem_acc_regulator_probe,
+ .remove = mem_acc_regulator_remove,
+ .driver = {
+ .name = "qcom,mem-acc-regulator",
+ .of_match_table = mem_acc_regulator_match_table,
+ .owner = THIS_MODULE,
+ },
+};
+
+int __init mem_acc_regulator_init(void)
+{
+ return platform_driver_register(&mem_acc_regulator_driver);
+}
+postcore_initcall(mem_acc_regulator_init);
+
+static void __exit mem_acc_regulator_exit(void)
+{
+ platform_driver_unregister(&mem_acc_regulator_driver);
+}
+module_exit(mem_acc_regulator_exit);
+
+MODULE_DESCRIPTION("MEM-ACC-SEL regulator driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/msm_gfx_ldo.c b/drivers/regulator/msm_gfx_ldo.c
new file mode 100644
index 0000000..2800607
--- /dev/null
+++ b/drivers/regulator/msm_gfx_ldo.c
@@ -0,0 +1,1652 @@
+/*
+ * 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) "GFX_LDO: %s: " fmt, __func__
+
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/msm-ldo-regulator.h>
+
+#define LDO_ATEST_REG 0x0
+#define LDO_CFG0_REG 0x4
+#define LDO_CFG1_REG 0x8
+#define LDO_CFG2_REG 0xC
+#define LDO_LD_DATA_REG 0x10
+
+#define LDO_VREF_TEST_CFG 0x14
+#define ENABLE_LDO_STATUS_BIT (BIT(8) | BIT(12))
+#define LDO_AUTOBYPASS_BIT BIT(20)
+
+#define LDO_VREF_SET_REG 0x18
+#define UPDATE_VREF_BIT BIT(31)
+#define SEL_RST_BIT BIT(16)
+#define VREF_VAL_MASK GENMASK(6, 0)
+
+#define PWRSWITCH_CTRL_REG 0x1C
+#define LDO_CLAMP_IO_BIT BIT(31)
+#define CPR_BYPASS_IN_LDO_MODE_BIT BIT(30)
+#define EN_LDOAP_CTRL_CPR_BIT BIT(29)
+#define CX_CPR_BYPASS_IN_LDO_MODE_BIT BIT(10)
+#define PWR_SRC_SEL_BIT BIT(9)
+#define ACK_SW_OVR_BIT BIT(8)
+#define LDO_PREON_SW_OVR_BIT BIT(7)
+#define LDO_BYPASS_BIT BIT(6)
+#define LDO_PDN_BIT BIT(5)
+#define LDO_UNDER_SW_CTRL_BIT BIT(4)
+#define BHS_EN_REST_BIT BIT(2)
+#define BHS_EN_FEW_BIT BIT(1)
+#define BHS_UNDER_SW_CTL BIT(0)
+
+#define LDO_STATUS1_REG 0x24
+
+#define PWRSWITCH_STATUS_REG 0x28
+#define LDO_VREF_SETTLED_BIT BIT(4)
+#define LDO_READY_BIT BIT(2)
+#define BHS_EN_REST_ACK_BIT BIT(1)
+
+#define REF_CURRENT_X1_REG 0x2C
+#define REF_CURRENT_X2_REG 0x30
+#define ADC_CTL_REG 0x34
+
+#define MIN_LDO_VOLTAGE 375000
+#define MAX_LDO_VOLTAGE 980000
+#define LDO_STEP_VOLATGE 5000
+
+#define MAX_LDO_REGS 11
+
+#define BYTES_PER_FUSE_ROW 8
+#define MAX_FUSE_ROW_BIT 63
+#define MIN_CORNER_OFFSET 1
+
+#define GFX_LDO_FUSE_STEP_VOLT 10000
+#define GFX_LDO_FUSE_SIZE 5
+
+enum direction {
+ NO_CHANGE,
+ UP,
+ DOWN,
+};
+
+enum voltage_handling {
+ VOLTAGE,
+ CORNER,
+};
+
+struct fuse_param {
+ unsigned int row;
+ unsigned int bit_start;
+ unsigned int bit_end;
+};
+
+struct ldo_config {
+ u32 offset;
+ u32 value;
+};
+
+struct msm_gfx_ldo {
+ struct device *dev;
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ struct regulator *vdd_cx;
+ struct regulator *mem_acc_vreg;
+ struct dentry *debugfs;
+
+ u32 num_corners;
+ u32 num_ldo_corners;
+ u32 *open_loop_volt;
+ u32 *ceiling_volt;
+ u32 *floor_volt;
+ u32 *ldo_corner_en_map;
+ u32 *vdd_cx_corner_map;
+ u32 *mem_acc_corner_map;
+ const int *ref_volt;
+ const struct fuse_param *ldo_enable_param;
+ const struct fuse_param **init_volt_param;
+ bool ldo_fuse_enable;
+ bool ldo_mode_disable;
+ struct ldo_config *ldo_init_config;
+
+ void __iomem *efuse_base;
+ phys_addr_t efuse_addr;
+ void __iomem *ldo_base;
+ phys_addr_t ldo_addr;
+
+ bool vreg_enabled;
+ enum msm_ldo_supply_mode mode;
+ u32 corner;
+ int ldo_voltage_uv;
+ struct mutex ldo_mutex;
+ enum voltage_handling ops_type;
+};
+
+#define MSM8953_LDO_FUSE_CORNERS 3
+#define LDO_MAX_OFFSET 0xFFFF
+static struct ldo_config msm8953_ldo_config[] = {
+ {LDO_ATEST_REG, 0x00000203},
+ {LDO_CFG0_REG, 0x05008600},
+ {LDO_CFG1_REG, 0x0},
+ {LDO_CFG2_REG, 0x0000C3FC},
+ {LDO_VREF_TEST_CFG, 0x004B1102},
+ {LDO_MAX_OFFSET, LDO_MAX_OFFSET},
+};
+
+static struct ldo_config sdm660_ldo_config[] = {
+ {LDO_ATEST_REG, 0x00000080},
+ {LDO_CFG0_REG, 0x0100A600},
+ {LDO_CFG1_REG, 0x000000A0},
+ {LDO_CFG2_REG, 0x0000C3FE},
+ {LDO_LD_DATA_REG, 0x00000000},
+ {LDO_VREF_TEST_CFG, 0x00401100},
+ {REF_CURRENT_X1_REG, 0x00000230},
+ {REF_CURRENT_X2_REG, 0x00000048},
+ {ADC_CTL_REG, 0x00000000},
+ {LDO_MAX_OFFSET, LDO_MAX_OFFSET},
+};
+
+static struct fuse_param msm8953_ldo_enable_param[] = {
+ {65, 11, 11},
+ {},
+};
+
+static const struct fuse_param
+msm8953_init_voltage_param[MSM8953_LDO_FUSE_CORNERS][2] = {
+ { {73, 42, 46}, {} },
+ { {73, 37, 41}, {} },
+ { {73, 32, 36}, {} },
+};
+
+static const int msm8953_fuse_ref_volt[MSM8953_LDO_FUSE_CORNERS] = {
+ 580000,
+ 650000,
+ 720000,
+};
+
+enum {
+ MSM8953_SOC_ID,
+ SDM660_SOC_ID,
+};
+
+static int 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;
+}
+
+static int read_fuse_param(void __iomem *fuse_base_addr,
+ const struct 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;
+}
+
+static enum msm_ldo_supply_mode get_operating_mode(struct msm_gfx_ldo *ldo_vreg,
+ int corner)
+{
+ if (!ldo_vreg->ldo_mode_disable && ldo_vreg->ldo_fuse_enable
+ && ldo_vreg->ldo_corner_en_map[corner])
+ return LDO_MODE;
+
+ return BHS_MODE;
+}
+
+static char *register_str[] = {
+ "LDO_ATEST",
+ "LDO_CFG0",
+ "LDO_CFG1",
+ "LDO_CFG2",
+ "LDO_LD_DATA",
+ "LDO_VREF_TEST_CFG",
+ "LDO_VREF_SET",
+ "PWRSWITCH_CTL",
+ "LDO_STATUS0",
+ "LDO_STATUS1",
+ "PWRSWITCH_STATUS",
+};
+
+static void dump_registers(struct msm_gfx_ldo *ldo_vreg, char *func)
+{
+ u32 reg[MAX_LDO_REGS];
+ int i;
+
+ for (i = 0; i < MAX_LDO_REGS; i++) {
+ reg[i] = 0;
+ reg[i] = readl_relaxed(ldo_vreg->ldo_base + (i * 4));
+ pr_debug("%s -- %s = 0x%x\n", func, register_str[i], reg[i]);
+ }
+}
+
+#define GET_VREF(a) DIV_ROUND_UP(a - MIN_LDO_VOLTAGE, LDO_STEP_VOLATGE)
+
+static void configure_ldo_voltage(struct msm_gfx_ldo *ldo_vreg, int new_uv)
+{
+ int val = 0;
+ u32 reg = 0;
+
+ val = GET_VREF(new_uv);
+ reg = readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* set the new voltage */
+ reg &= ~VREF_VAL_MASK;
+ reg |= val & VREF_VAL_MASK;
+ writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* Initiate VREF update */
+ reg |= UPDATE_VREF_BIT;
+ writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* complete the writes */
+ mb();
+
+ reg &= ~UPDATE_VREF_BIT;
+ writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ ldo_vreg->ldo_voltage_uv = new_uv;
+
+ /* complete the write sequence */
+ mb();
+}
+
+static int ldo_update_voltage(struct msm_gfx_ldo *ldo_vreg, int new_uv)
+{
+ int timeout = 50;
+ u32 reg = 0;
+
+ configure_ldo_voltage(ldo_vreg, new_uv);
+
+ while (--timeout) {
+ reg = readl_relaxed(ldo_vreg->ldo_base +
+ PWRSWITCH_STATUS_REG);
+ if (reg & (LDO_VREF_SETTLED_BIT | LDO_READY_BIT))
+ break;
+
+ udelay(10);
+ }
+ if (!timeout) {
+ pr_err("LDO_VREF_SETTLED not set PWRSWITCH_STATUS = 0x%x\n",
+ reg);
+ return -EBUSY;
+ }
+
+ pr_debug("LDO voltage set to=%d uV VREF_REG=%x\n",
+ ldo_vreg->ldo_voltage_uv,
+ readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG));
+
+ return 0;
+}
+
+static int enable_ldo_mode(struct msm_gfx_ldo *ldo_vreg, int new_uv)
+{
+ u32 ctl = 0;
+
+ /* set the ldo-vref */
+ configure_ldo_voltage(ldo_vreg, new_uv);
+
+ /* configure the LDO for power-up */
+ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Move BHS under SW control */
+ ctl |= BHS_UNDER_SW_CTL;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Set LDO under gdsc control */
+ ctl &= ~LDO_UNDER_SW_CTRL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* enable hw_pre-on to gdsc */
+ ctl |= LDO_PREON_SW_OVR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* remove LDO bypass */
+ ctl &= ~LDO_BYPASS_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* set power-source as LDO */
+ ctl |= PWR_SRC_SEL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* clear fake-sw ack to gdsc */
+ ctl &= ~ACK_SW_OVR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* put CPR in bypass mode */
+ if (ldo_vreg->ops_type == CORNER) {
+ ctl |= CPR_BYPASS_IN_LDO_MODE_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ }
+
+ /* complete all writes */
+ mb();
+
+ dump_registers(ldo_vreg, "enable_ldo_mode");
+
+ return 0;
+}
+
+static int enable_bhs_mode(struct msm_gfx_ldo *ldo_vreg)
+{
+ u32 ctl = 0;
+
+ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Put LDO under SW control */
+ ctl |= LDO_UNDER_SW_CTRL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* set power-source as BHS */
+ ctl &= ~PWR_SRC_SEL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ if (ldo_vreg->ops_type == CORNER) {
+ /* clear GFX CPR in by-pass mode */
+ ctl &= ~CPR_BYPASS_IN_LDO_MODE_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ }
+
+ /* Enable the BHS control signals to gdsc */
+ ctl &= ~BHS_EN_FEW_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ ctl &= ~BHS_EN_REST_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Put BHS under GDSC control */
+ ctl &= ~BHS_UNDER_SW_CTL;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ dump_registers(ldo_vreg, "enable_bhs_mode");
+
+ return 0;
+}
+
+static int msm_gfx_ldo_corner_enable(struct regulator_dev *rdev)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+ int rc = 0, new_uv;
+ enum msm_ldo_supply_mode enable_mode;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ pr_debug("regulator_enable requested. corner=%d\n",
+ ldo_vreg->corner + MIN_CORNER_OFFSET);
+
+ if (ldo_vreg->vdd_cx) {
+ rc = regulator_set_voltage(ldo_vreg->vdd_cx,
+ ldo_vreg->vdd_cx_corner_map[ldo_vreg->corner],
+ INT_MAX);
+ if (rc) {
+ pr_err("Unable to set CX for corner %d rc=%d\n",
+ ldo_vreg->corner + MIN_CORNER_OFFSET, rc);
+ goto fail;
+ }
+
+ rc = regulator_enable(ldo_vreg->vdd_cx);
+ if (rc) {
+ pr_err("regulator_enable: vdd_cx: failed rc=%d\n", rc);
+ goto fail;
+ }
+ }
+
+ enable_mode = get_operating_mode(ldo_vreg, ldo_vreg->corner);
+ if (enable_mode == LDO_MODE) {
+ new_uv = ldo_vreg->open_loop_volt[ldo_vreg->corner];
+ rc = enable_ldo_mode(ldo_vreg, new_uv);
+ pr_debug("LDO voltage configured =%d uV corner=%d\n",
+ ldo_vreg->ldo_voltage_uv,
+ ldo_vreg->corner + MIN_CORNER_OFFSET);
+ } else {
+ rc = enable_bhs_mode(ldo_vreg);
+ }
+
+ if (rc) {
+ pr_err("Failed to enable regulator in %s mode rc=%d\n",
+ (enable_mode == LDO_MODE) ? "LDO" : "BHS", rc);
+ goto disable_cx;
+ }
+
+ pr_debug("regulator_enable complete. mode=%s, corner=%d\n",
+ (enable_mode == LDO_MODE) ? "LDO" : "BHS",
+ ldo_vreg->corner + MIN_CORNER_OFFSET);
+
+ ldo_vreg->mode = enable_mode;
+ ldo_vreg->vreg_enabled = true;
+
+disable_cx:
+ if (rc && ldo_vreg->vdd_cx) {
+ rc = regulator_disable(ldo_vreg->vdd_cx);
+ if (rc)
+ pr_err("regulator_enable: vdd_cx: failed rc=%d\n", rc);
+ }
+fail:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int msm_gfx_ldo_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ if (ldo_vreg->vdd_cx) {
+ rc = regulator_disable(ldo_vreg->vdd_cx);
+ if (rc) {
+ pr_err("regulator_disable: vdd_cx: failed rc=%d\n", rc);
+ goto done;
+ }
+ rc = regulator_set_voltage(ldo_vreg->vdd_cx, 0, INT_MAX);
+ if (rc)
+ pr_err("failed to set voltage on CX rc=%d\n", rc);
+ }
+
+ /* No additional configuration for LDO/BHS - taken care by gsdc */
+ ldo_vreg->vreg_enabled = false;
+
+ pr_debug("regulator_disabled complete\n");
+done:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int switch_mode_to_ldo(struct msm_gfx_ldo *ldo_vreg, int new_uv)
+{
+ u32 ctl = 0, status = 0, timeout = 50;
+
+ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ if (ldo_vreg->ops_type == CORNER) {
+ /* enable CPR bypass mode for LDO */
+ ctl |= CPR_BYPASS_IN_LDO_MODE_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ }
+
+ /* fake ack to GDSC */
+ ctl |= ACK_SW_OVR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* set power-source as LDO */
+ ctl |= PWR_SRC_SEL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Make sure BHS continues to power the rail */
+ ctl |= BHS_EN_FEW_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ ctl |= BHS_EN_REST_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* move BHS to SW control */
+ ctl |= BHS_UNDER_SW_CTL;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* set LDO under SW control */
+ ctl |= LDO_UNDER_SW_CTRL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* bypass LDO */
+ ctl |= LDO_BYPASS_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* power-on LDO */
+ ctl &= ~LDO_PDN_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* set the new LDO voltage */
+ ldo_update_voltage(ldo_vreg, new_uv);
+
+ pr_debug("LDO voltage =%d uV\n", ldo_vreg->ldo_voltage_uv);
+
+ /* make sure that the configuration is complete */
+ mb();
+
+ /* power down BHS */
+ ctl &= ~BHS_EN_FEW_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ ctl &= ~BHS_EN_REST_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* make sure that the configuration is complete */
+ mb();
+
+ /* wait for BHS to turn-off */
+ while (--timeout) {
+ status = readl_relaxed(ldo_vreg->ldo_base +
+ PWRSWITCH_STATUS_REG);
+ if (!(status & BHS_EN_REST_ACK_BIT))
+ break;
+
+ udelay(10);
+ }
+
+ if (!timeout)
+ pr_err("BHS_EN_RESET_ACK not clear PWRSWITCH_STATUS = 0x%x\n",
+ status);
+
+ /* remove LDO bypass */
+ ctl &= ~LDO_BYPASS_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* expose LDO to gdsc */
+ ctl &= ~ACK_SW_OVR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ ctl &= ~LDO_UNDER_SW_CTRL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ dump_registers(ldo_vreg, "switch_mode_to_ldo");
+
+ return 0;
+}
+
+static int switch_mode_to_bhs(struct msm_gfx_ldo *ldo_vreg)
+{
+ u32 ctl = 0, status = 0, timeout = 50;
+
+ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* fake ack to gdsc */
+ ctl |= ACK_SW_OVR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* select BHS as power source */
+ ctl &= ~PWR_SRC_SEL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* LDO stays ON */
+ ctl &= ~LDO_PDN_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Move LDO to SW control */
+ ctl |= LDO_UNDER_SW_CTRL_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Power-up BHS */
+ ctl |= BHS_EN_FEW_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ ctl |= BHS_EN_REST_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* make sure that the configuration is complete */
+ mb();
+
+ /* wait for BHS to power-up */
+ while (--timeout) {
+ status = readl_relaxed(ldo_vreg->ldo_base +
+ PWRSWITCH_STATUS_REG);
+ if (status & BHS_EN_REST_ACK_BIT)
+ break;
+
+ udelay(10);
+ }
+ if (!timeout)
+ pr_err("BHS_EN_RESET_ACK not set PWRSWITCH_STATUS = 0x%x\n",
+ status);
+
+ /* bypass LDO */
+ ctl |= LDO_BYPASS_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* pull-down LDO */
+ ctl |= LDO_PDN_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ /* Expose BHS to gdsc */
+ ctl &= ~ACK_SW_OVR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ ctl &= ~BHS_UNDER_SW_CTL;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ if (ldo_vreg->ops_type == CORNER) {
+ /* Enable CPR in BHS mode */
+ ctl &= ~CPR_BYPASS_IN_LDO_MODE_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ }
+
+ /* make sure that all configuration is complete */
+ mb();
+
+ dump_registers(ldo_vreg, "switch_mode_to_bhs");
+
+ return 0;
+}
+
+static int msm_gfx_ldo_set_corner(struct regulator_dev *rdev,
+ int corner, int corner_max, unsigned int *selector)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+ int rc = 0, mem_acc_corner, new_uv;
+ enum msm_ldo_supply_mode new_mode;
+ enum direction dir = NO_CHANGE;
+
+ corner -= MIN_CORNER_OFFSET;
+ corner_max -= MIN_CORNER_OFFSET;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ if (corner == ldo_vreg->corner)
+ goto done;
+
+ pr_debug("set-voltage requested: old_mode=%s old_corner=%d new_corner=%d vreg_enabled=%d\n",
+ ldo_vreg->mode == BHS_MODE ? "BHS" : "LDO",
+ ldo_vreg->corner + MIN_CORNER_OFFSET,
+ corner + MIN_CORNER_OFFSET,
+ ldo_vreg->vreg_enabled);
+
+ if (corner > ldo_vreg->corner)
+ dir = UP;
+ else if (corner < ldo_vreg->corner)
+ dir = DOWN;
+
+ if (ldo_vreg->mem_acc_vreg && dir == DOWN) {
+ mem_acc_corner = ldo_vreg->mem_acc_corner_map[corner];
+ rc = regulator_set_voltage(ldo_vreg->mem_acc_vreg,
+ mem_acc_corner, mem_acc_corner);
+ }
+
+ if (!ldo_vreg->vreg_enabled) {
+ ldo_vreg->corner = corner;
+ goto done;
+ }
+
+ if (ldo_vreg->vdd_cx) {
+ rc = regulator_set_voltage(ldo_vreg->vdd_cx,
+ ldo_vreg->vdd_cx_corner_map[corner],
+ INT_MAX);
+ if (rc) {
+ pr_err("Unable to set CX for corner %d rc=%d\n",
+ corner + MIN_CORNER_OFFSET, rc);
+ goto done;
+ }
+ }
+
+ new_mode = get_operating_mode(ldo_vreg, corner);
+
+ if (new_mode == BHS_MODE) {
+ if (ldo_vreg->mode == LDO_MODE) {
+ rc = switch_mode_to_bhs(ldo_vreg);
+ if (rc)
+ pr_err("Switch to BHS corner=%d failed rc=%d\n",
+ corner + MIN_CORNER_OFFSET, rc);
+ }
+ } else { /* new mode - LDO */
+ new_uv = ldo_vreg->open_loop_volt[ldo_vreg->corner];
+
+ if (ldo_vreg->mode == BHS_MODE) {
+ rc = switch_mode_to_ldo(ldo_vreg, new_uv);
+ if (rc)
+ pr_err("Switch to LDO failed corner=%d rc=%d\n",
+ corner + MIN_CORNER_OFFSET, rc);
+ } else {
+ rc = ldo_update_voltage(ldo_vreg, new_uv);
+ if (rc)
+ pr_err("Update voltage failed corner=%d rc=%d\n",
+ corner + MIN_CORNER_OFFSET, rc);
+ }
+ }
+
+ if (!rc) {
+ pr_debug("set-voltage complete. old_mode=%s new_mode=%s old_corner=%d new_corner=%d\n",
+ ldo_vreg->mode == BHS_MODE ? "BHS" : "LDO",
+ new_mode == BHS_MODE ? "BHS" : "LDO",
+ ldo_vreg->corner + MIN_CORNER_OFFSET,
+ corner + MIN_CORNER_OFFSET);
+
+ ldo_vreg->mode = new_mode;
+ ldo_vreg->corner = corner;
+ }
+
+done:
+ if (!rc && ldo_vreg->mem_acc_vreg && dir == UP) {
+ mem_acc_corner = ldo_vreg->mem_acc_corner_map[corner];
+ rc = regulator_set_voltage(ldo_vreg->mem_acc_vreg,
+ mem_acc_corner, mem_acc_corner);
+ }
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int msm_gfx_ldo_get_corner(struct regulator_dev *rdev)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+
+ return ldo_vreg->corner + MIN_CORNER_OFFSET;
+}
+
+static int msm_gfx_ldo_is_enabled(struct regulator_dev *rdev)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+
+ return ldo_vreg->vreg_enabled;
+}
+
+static struct regulator_ops msm_gfx_ldo_corner_ops = {
+ .enable = msm_gfx_ldo_corner_enable,
+ .disable = msm_gfx_ldo_disable,
+ .is_enabled = msm_gfx_ldo_is_enabled,
+ .set_voltage = msm_gfx_ldo_set_corner,
+ .get_voltage = msm_gfx_ldo_get_corner,
+};
+
+static int msm_gfx_ldo_get_bypass(struct regulator_dev *rdev,
+ bool *enable)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+
+ *enable = ldo_vreg->mode;
+
+ return 0;
+}
+
+static int msm_gfx_ldo_set_bypass(struct regulator_dev *rdev,
+ bool mode)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ if (ldo_vreg->mode == mode || !ldo_vreg->vreg_enabled)
+ goto done;
+
+ if (mode == LDO_MODE)
+ rc = switch_mode_to_ldo(ldo_vreg, ldo_vreg->ldo_voltage_uv);
+ else
+ rc = switch_mode_to_bhs(ldo_vreg);
+
+ if (rc) {
+ pr_err("Failed to configure regulator in %s mode rc=%d\n",
+ (mode == LDO_MODE) ? "LDO" : "BHS", rc);
+ goto done;
+ }
+
+ pr_debug("regulator_set_bypass complete. mode=%s, voltage = %d uV\n",
+ (mode == LDO_MODE) ? "LDO" : "BHS",
+ (mode == LDO_MODE) ? ldo_vreg->ldo_voltage_uv : 0);
+
+ ldo_vreg->mode = mode;
+
+done:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int msm_gfx_ldo_voltage_enable(struct regulator_dev *rdev)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+ int rc = 0;
+ enum msm_ldo_supply_mode enable_mode;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ pr_debug("regulator_enable requested. voltage=%d\n",
+ ldo_vreg->ldo_voltage_uv);
+
+ enable_mode = ldo_vreg->mode;
+
+ if (enable_mode == LDO_MODE)
+ rc = enable_ldo_mode(ldo_vreg, ldo_vreg->ldo_voltage_uv);
+ else
+ rc = enable_bhs_mode(ldo_vreg);
+
+ if (rc) {
+ pr_err("Failed to enable regulator in %s mode rc=%d\n",
+ (enable_mode == LDO_MODE) ? "LDO" : "BHS", rc);
+ goto fail;
+ }
+
+ pr_debug("regulator_enable complete. mode=%s, voltage = %d uV\n",
+ (enable_mode == LDO_MODE) ? "LDO" : "BHS",
+ (enable_mode == LDO_MODE) ? ldo_vreg->ldo_voltage_uv : 0);
+
+ ldo_vreg->vreg_enabled = true;
+
+fail:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int msm_gfx_ldo_set_voltage(struct regulator_dev *rdev,
+ int new_uv, int max_uv, unsigned int *selector)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+ int rc = 0;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ if (new_uv == ldo_vreg->ldo_voltage_uv)
+ goto done;
+
+ if (!ldo_vreg->vreg_enabled || ldo_vreg->mode != LDO_MODE) {
+ ldo_vreg->ldo_voltage_uv = new_uv;
+ goto done;
+ }
+
+ /* update LDO voltage */
+ rc = ldo_update_voltage(ldo_vreg, new_uv);
+ if (rc)
+ pr_err("Update voltage failed for [%d, %d], rc=%d\n",
+ new_uv, max_uv, rc);
+done:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int msm_gfx_ldo_get_voltage(struct regulator_dev *rdev)
+{
+ struct msm_gfx_ldo *ldo_vreg = rdev_get_drvdata(rdev);
+
+ return ldo_vreg->ldo_voltage_uv;
+}
+
+static struct regulator_ops msm_gfx_ldo_voltage_ops = {
+ .enable = msm_gfx_ldo_voltage_enable,
+ .disable = msm_gfx_ldo_disable,
+ .is_enabled = msm_gfx_ldo_is_enabled,
+ .set_voltage = msm_gfx_ldo_set_voltage,
+ .get_voltage = msm_gfx_ldo_get_voltage,
+ .set_bypass = msm_gfx_ldo_set_bypass,
+ .get_bypass = msm_gfx_ldo_get_bypass,
+};
+
+static int msm_gfx_ldo_adjust_init_voltage(struct msm_gfx_ldo *ldo_vreg)
+{
+ int rc, len, size, i;
+ u32 *volt_adjust;
+ struct device_node *of_node = ldo_vreg->dev->of_node;
+ char *prop_name = "qcom,ldo-init-voltage-adjustment";
+
+ if (!of_find_property(of_node, prop_name, &len)) {
+ /* No initial voltage adjustment needed. */
+ return 0;
+ }
+
+ size = len / sizeof(u32);
+ if (size != ldo_vreg->num_ldo_corners) {
+ pr_err("%s length=%d is invalid: required:%d\n",
+ prop_name, size, ldo_vreg->num_ldo_corners);
+ return -EINVAL;
+ }
+
+ volt_adjust = devm_kcalloc(ldo_vreg->dev, size, sizeof(*volt_adjust),
+ GFP_KERNEL);
+ if (!volt_adjust)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, prop_name, volt_adjust, size);
+ if (rc) {
+ pr_err("failed to read %s property rc=%d\n", prop_name, rc);
+ return rc;
+ }
+
+ for (i = 0; i < ldo_vreg->num_corners; i++) {
+ if (volt_adjust[i]) {
+ ldo_vreg->open_loop_volt[i] += volt_adjust[i];
+ pr_info("adjusted the open-loop voltage[%d] %d -> %d\n",
+ i + MIN_CORNER_OFFSET,
+ ldo_vreg->open_loop_volt[i] - volt_adjust[i],
+ ldo_vreg->open_loop_volt[i]);
+ }
+ }
+
+ return 0;
+}
+
+static int msm_gfx_ldo_voltage_init(struct msm_gfx_ldo *ldo_vreg)
+{
+ struct device_node *of_node = ldo_vreg->dev->of_node;
+ int i, rc, len;
+ u64 efuse_bits;
+
+ len = ldo_vreg->num_ldo_corners;
+
+ ldo_vreg->open_loop_volt = devm_kcalloc(ldo_vreg->dev,
+ len, sizeof(*ldo_vreg->open_loop_volt),
+ GFP_KERNEL);
+ ldo_vreg->ceiling_volt = devm_kcalloc(ldo_vreg->dev,
+ len, sizeof(*ldo_vreg->ceiling_volt),
+ GFP_KERNEL);
+ ldo_vreg->floor_volt = devm_kcalloc(ldo_vreg->dev,
+ len, sizeof(*ldo_vreg->floor_volt),
+ GFP_KERNEL);
+
+ if (!ldo_vreg->open_loop_volt || !ldo_vreg->ceiling_volt
+ || !ldo_vreg->floor_volt)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, "qcom,ldo-voltage-ceiling",
+ ldo_vreg->ceiling_volt, len);
+ if (rc) {
+ pr_err("Unable to read qcom,ldo-voltage-ceiling rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32_array(of_node, "qcom,ldo-voltage-floor",
+ ldo_vreg->floor_volt, len);
+ if (rc) {
+ pr_err("Unable to read qcom,ldo-voltage-floor rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < ldo_vreg->num_ldo_corners; i++) {
+ rc = read_fuse_param(ldo_vreg->efuse_base,
+ ldo_vreg->init_volt_param[i],
+ &efuse_bits);
+ if (rc) {
+ pr_err("Unable to read init-voltage rc=%d\n", rc);
+ return rc;
+ }
+ ldo_vreg->open_loop_volt[i] = convert_open_loop_voltage_fuse(
+ ldo_vreg->ref_volt[i],
+ GFX_LDO_FUSE_STEP_VOLT,
+ efuse_bits,
+ GFX_LDO_FUSE_SIZE);
+ pr_info("LDO corner %d: target-volt = %d uV\n",
+ i + MIN_CORNER_OFFSET, ldo_vreg->open_loop_volt[i]);
+ }
+
+ rc = msm_gfx_ldo_adjust_init_voltage(ldo_vreg);
+ if (rc) {
+ pr_err("Unable to adjust init voltages rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < ldo_vreg->num_ldo_corners; i++) {
+ if (ldo_vreg->open_loop_volt[i] > ldo_vreg->ceiling_volt[i]) {
+ pr_info("Warning: initial voltage[%d] %d above ceiling %d\n",
+ i + MIN_CORNER_OFFSET,
+ ldo_vreg->open_loop_volt[i],
+ ldo_vreg->ceiling_volt[i]);
+ ldo_vreg->open_loop_volt[i] = ldo_vreg->ceiling_volt[i];
+ } else if (ldo_vreg->open_loop_volt[i] <
+ ldo_vreg->floor_volt[i]) {
+ pr_info("Warning: initial voltage[%d] %d below floor %d\n",
+ i + MIN_CORNER_OFFSET,
+ ldo_vreg->open_loop_volt[i],
+ ldo_vreg->floor_volt[i]);
+ ldo_vreg->open_loop_volt[i] = ldo_vreg->floor_volt[i];
+ }
+ }
+
+ efuse_bits = 0;
+ rc = read_fuse_param(ldo_vreg->efuse_base, ldo_vreg->ldo_enable_param,
+ &efuse_bits);
+ if (rc) {
+ pr_err("Unable to read ldo_enable_param rc=%d\n", rc);
+ return rc;
+ }
+ ldo_vreg->ldo_fuse_enable = !!efuse_bits;
+ pr_info("LDO-mode fuse %s by default\n", ldo_vreg->ldo_fuse_enable ?
+ "enabled" : "disabled");
+
+ return rc;
+}
+
+static int msm_gfx_ldo_efuse_init(struct platform_device *pdev,
+ struct msm_gfx_ldo *ldo_vreg)
+{
+ struct resource *res;
+ u32 len;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr");
+ if (!res || !res->start) {
+ pr_err("efuse_addr missing: res=%p\n", res);
+ return -EINVAL;
+ }
+
+ ldo_vreg->efuse_addr = res->start;
+ len = res->end - res->start + 1;
+
+ ldo_vreg->efuse_base = devm_ioremap(&pdev->dev,
+ ldo_vreg->efuse_addr, len);
+ if (!ldo_vreg->efuse_base) {
+ pr_err("Unable to map efuse_addr %pa\n",
+ &ldo_vreg->efuse_addr);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int msm_gfx_ldo_mem_acc_init(struct msm_gfx_ldo *ldo_vreg)
+{
+ int rc;
+ u32 len, size;
+ struct device_node *of_node = ldo_vreg->dev->of_node;
+
+ if (of_find_property(ldo_vreg->dev->of_node, "mem-acc-supply", NULL)) {
+ ldo_vreg->mem_acc_vreg = devm_regulator_get(ldo_vreg->dev,
+ "mem-acc");
+ if (IS_ERR_OR_NULL(ldo_vreg->mem_acc_vreg)) {
+ rc = PTR_RET(ldo_vreg->mem_acc_vreg);
+ if (rc != -EPROBE_DEFER)
+ pr_err("devm_regulator_get: mem-acc: rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ pr_debug("mem-acc-supply not specified\n");
+ return 0;
+ }
+
+ if (!of_find_property(of_node, "qcom,mem-acc-corner-map", &len)) {
+ pr_err("qcom,mem-acc-corner-map missing");
+ return -EINVAL;
+ }
+
+ size = len / sizeof(u32);
+ if (size != ldo_vreg->num_corners) {
+ pr_err("qcom,mem-acc-corner-map length=%d is invalid: required:%u\n",
+ size, ldo_vreg->num_corners);
+ return -EINVAL;
+ }
+
+ ldo_vreg->mem_acc_corner_map = devm_kcalloc(ldo_vreg->dev, size,
+ sizeof(*ldo_vreg->mem_acc_corner_map), GFP_KERNEL);
+ if (!ldo_vreg->mem_acc_corner_map)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, "qcom,mem-acc-corner-map",
+ ldo_vreg->mem_acc_corner_map, size);
+ if (rc)
+ pr_err("Unable to read qcom,mem-acc-corner-map rc=%d\n", rc);
+
+ return rc;
+}
+
+static int msm_gfx_ldo_init(struct platform_device *pdev,
+ struct msm_gfx_ldo *ldo_vreg)
+{
+ struct resource *res;
+ u32 len, ctl;
+ int i = 0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ldo_addr");
+ if (!res || !res->start) {
+ pr_err("ldo_addr missing: res=%p\n", res);
+ return -EINVAL;
+ }
+
+ ldo_vreg->ldo_addr = res->start;
+ len = res->end - res->start + 1;
+
+ ldo_vreg->ldo_base = devm_ioremap(ldo_vreg->dev,
+ ldo_vreg->ldo_addr, len);
+ if (!ldo_vreg->ldo_base) {
+ pr_err("Unable to map efuse_addr %pa\n",
+ &ldo_vreg->ldo_addr);
+ return -EINVAL;
+ }
+
+ /* HW initialization */
+
+ /* clear clamp_io, enable CPR in auto-bypass*/
+ ctl = readl_relaxed(ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+ ctl &= ~LDO_CLAMP_IO_BIT;
+ ctl |= EN_LDOAP_CTRL_CPR_BIT;
+ writel_relaxed(ctl, ldo_vreg->ldo_base + PWRSWITCH_CTRL_REG);
+
+ i = 0;
+ while (ldo_vreg->ldo_init_config &&
+ ldo_vreg->ldo_init_config[i].offset != LDO_MAX_OFFSET) {
+ writel_relaxed(ldo_vreg->ldo_init_config[i].value,
+ ldo_vreg->ldo_base +
+ ldo_vreg->ldo_init_config[i].offset);
+ i++;
+ }
+ /* complete the writes */
+ mb();
+
+ return 0;
+}
+
+static int ldo_parse_cx_parameters(struct msm_gfx_ldo *ldo_vreg)
+{
+ struct device_node *of_node = ldo_vreg->dev->of_node;
+ int rc, len, size;
+
+ if (of_find_property(of_node, "vdd-cx-supply", NULL)) {
+ ldo_vreg->vdd_cx = devm_regulator_get(ldo_vreg->dev, "vdd-cx");
+ if (IS_ERR_OR_NULL(ldo_vreg->vdd_cx)) {
+ rc = PTR_RET(ldo_vreg->vdd_cx);
+ if (rc != -EPROBE_DEFER)
+ pr_err("devm_regulator_get: vdd-cx: rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
+ pr_debug("vdd-cx-supply not specified\n");
+ return 0;
+ }
+
+ if (!of_find_property(of_node, "qcom,vdd-cx-corner-map", &len)) {
+ pr_err("qcom,vdd-cx-corner-map missing");
+ return -EINVAL;
+ }
+
+ size = len / sizeof(u32);
+ if (size != ldo_vreg->num_corners) {
+ pr_err("qcom,vdd-cx-corner-map length=%d is invalid: required:%u\n",
+ size, ldo_vreg->num_corners);
+ return -EINVAL;
+ }
+
+ ldo_vreg->vdd_cx_corner_map = devm_kcalloc(ldo_vreg->dev, size,
+ sizeof(*ldo_vreg->vdd_cx_corner_map), GFP_KERNEL);
+ if (!ldo_vreg->vdd_cx_corner_map)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, "qcom,vdd-cx-corner-map",
+ ldo_vreg->vdd_cx_corner_map, size);
+ if (rc)
+ pr_err("Unable to read qcom,vdd-cx-corner-map rc=%d\n", rc);
+
+
+ return rc;
+}
+
+static int msm_gfx_ldo_parse_dt(struct msm_gfx_ldo *ldo_vreg)
+{
+ struct device_node *of_node = ldo_vreg->dev->of_node;
+ int rc, size, len;
+
+ rc = of_property_read_u32(of_node, "qcom,num-corners",
+ &ldo_vreg->num_corners);
+ if (rc < 0) {
+ pr_err("Unable to read qcom,num-corners rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,num-ldo-corners",
+ &ldo_vreg->num_ldo_corners);
+ if (rc) {
+ pr_err("Unable to read qcom,num-ldo-corners rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = of_property_read_u32(of_node, "qcom,init-corner",
+ &ldo_vreg->corner);
+ if (rc) {
+ pr_err("Unable to read qcom,init-corner rc=%d\n", rc);
+ return rc;
+ }
+
+ if (!of_find_property(of_node, "qcom,ldo-enable-corner-map", &len)) {
+ pr_err("qcom,ldo-enable-corner-map missing\n");
+ return -EINVAL;
+ }
+
+ size = len / sizeof(u32);
+ if (size != ldo_vreg->num_corners) {
+ pr_err("qcom,ldo-enable-corner-map length=%d is invalid: required:%u\n",
+ size, ldo_vreg->num_corners);
+ return -EINVAL;
+ }
+
+ ldo_vreg->ldo_corner_en_map = devm_kcalloc(ldo_vreg->dev, size,
+ sizeof(*ldo_vreg->ldo_corner_en_map), GFP_KERNEL);
+ if (!ldo_vreg->ldo_corner_en_map)
+ return -ENOMEM;
+
+ rc = of_property_read_u32_array(of_node, "qcom,ldo-enable-corner-map",
+ ldo_vreg->ldo_corner_en_map, size);
+ if (rc) {
+ pr_err("Unable to read qcom,ldo-enable-corner-map rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = ldo_parse_cx_parameters(ldo_vreg);
+ if (rc) {
+ pr_err("Unable to parse CX parameters rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int msm_gfx_ldo_target_init(struct msm_gfx_ldo *ldo_vreg)
+{
+ int i;
+
+ /* MSM8953 */
+ ldo_vreg->init_volt_param = devm_kzalloc(ldo_vreg->dev,
+ (MSM8953_LDO_FUSE_CORNERS *
+ sizeof(struct fuse_param *)), GFP_KERNEL);
+ if (!ldo_vreg->init_volt_param)
+ return -ENOMEM;
+
+ for (i = 0; i < MSM8953_LDO_FUSE_CORNERS; i++)
+ ldo_vreg->init_volt_param[i] =
+ msm8953_init_voltage_param[i];
+
+ ldo_vreg->ref_volt = msm8953_fuse_ref_volt;
+ ldo_vreg->ldo_enable_param = msm8953_ldo_enable_param;
+
+ return 0;
+}
+
+static int debugfs_ldo_mode_disable_set(void *data, u64 val)
+{
+ struct msm_gfx_ldo *ldo_vreg = data;
+
+ ldo_vreg->ldo_mode_disable = !!val;
+
+ pr_debug("LDO-mode %s\n", ldo_vreg->ldo_mode_disable ?
+ "disabled" : "enabled");
+
+ return 0;
+}
+
+static int debugfs_ldo_mode_disable_get(void *data, u64 *val)
+{
+ struct msm_gfx_ldo *ldo_vreg = data;
+
+ *val = ldo_vreg->ldo_mode_disable;
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(ldo_mode_disable_fops, debugfs_ldo_mode_disable_get,
+ debugfs_ldo_mode_disable_set, "%llu\n");
+
+static int debugfs_ldo_set_voltage(void *data, u64 val)
+{
+ struct msm_gfx_ldo *ldo_vreg = data;
+ int rc = 0, timeout = 50;
+ u32 reg = 0, voltage = 0;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ if (ldo_vreg->mode == BHS_MODE || !ldo_vreg->vreg_enabled ||
+ val > MAX_LDO_VOLTAGE || val < MIN_LDO_VOLTAGE) {
+ rc = -EINVAL;
+ goto done;
+ }
+ voltage = GET_VREF((u32)val);
+
+ reg = readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* set the new voltage */
+ reg &= ~VREF_VAL_MASK;
+ reg |= voltage & VREF_VAL_MASK;
+ writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* Initiate VREF update */
+ reg |= UPDATE_VREF_BIT;
+ writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* complete the writes */
+ mb();
+
+ reg &= ~UPDATE_VREF_BIT;
+ writel_relaxed(reg, ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+
+ /* complete the writes */
+ mb();
+
+ while (--timeout) {
+ reg = readl_relaxed(ldo_vreg->ldo_base +
+ PWRSWITCH_STATUS_REG);
+ if (reg & (LDO_VREF_SETTLED_BIT | LDO_READY_BIT))
+ break;
+
+ udelay(10);
+ }
+ if (!timeout) {
+ pr_err("LDO_VREF_SETTLED not set PWRSWITCH_STATUS = 0x%x\n",
+ reg);
+ rc = -EBUSY;
+ } else {
+ ldo_vreg->ldo_voltage_uv = val;
+ pr_debug("LDO voltage set to %d uV\n",
+ ldo_vreg->ldo_voltage_uv);
+ }
+done:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+
+static int debugfs_ldo_get_voltage(void *data, u64 *val)
+{
+ struct msm_gfx_ldo *ldo_vreg = data;
+ int rc = 0;
+ u32 reg;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ if (ldo_vreg->mode == BHS_MODE || !ldo_vreg->vreg_enabled) {
+ rc = -EINVAL;
+ goto done;
+ }
+
+ reg = readl_relaxed(ldo_vreg->ldo_base + LDO_VREF_SET_REG);
+ reg &= VREF_VAL_MASK;
+
+ *val = (reg * LDO_STEP_VOLATGE) + MIN_LDO_VOLTAGE;
+done:
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(ldo_voltage_fops, debugfs_ldo_get_voltage,
+ debugfs_ldo_set_voltage, "%llu\n");
+
+static int msm_gfx_ldo_debug_info_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+
+ return 0;
+}
+
+static ssize_t msm_gfx_ldo_debug_info_read(struct file *file, char __user *buff,
+ size_t count, loff_t *ppos)
+{
+ struct msm_gfx_ldo *ldo_vreg = file->private_data;
+ char *debugfs_buf;
+ ssize_t len, ret = 0;
+ u32 i = 0, reg[MAX_LDO_REGS];
+
+ debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!debugfs_buf)
+ return -ENOMEM;
+
+ mutex_lock(&ldo_vreg->ldo_mutex);
+
+ len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+ "Regulator_enable = %d Regulator mode = %s Corner = %d LDO-voltage = %d uV\n",
+ ldo_vreg->vreg_enabled,
+ ldo_vreg->mode == BHS_MODE ? "BHS" : "LDO",
+ ldo_vreg->corner + MIN_CORNER_OFFSET,
+ ldo_vreg->ldo_voltage_uv);
+ ret += len;
+
+ for (i = 0; i < MAX_LDO_REGS; i++) {
+ reg[i] = 0;
+ reg[i] = readl_relaxed(ldo_vreg->ldo_base + (i * 4));
+ len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+ "%s = 0x%x\n", register_str[i], reg[i]);
+ ret += len;
+ }
+
+ mutex_unlock(&ldo_vreg->ldo_mutex);
+
+ ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret);
+ kfree(debugfs_buf);
+
+ return ret;
+}
+
+static const struct file_operations msm_gfx_ldo_debug_info_fops = {
+ .open = msm_gfx_ldo_debug_info_open,
+ .read = msm_gfx_ldo_debug_info_read,
+};
+
+static void msm_gfx_ldo_debugfs_init(struct msm_gfx_ldo *ldo_vreg)
+{
+ struct dentry *temp;
+
+ ldo_vreg->debugfs = debugfs_create_dir("msm_gfx_ldo", NULL);
+ if (!ldo_vreg->debugfs) {
+ pr_err("Couldn't create debug dir\n");
+ return;
+ }
+
+ temp = debugfs_create_file("debug_info", 0444, ldo_vreg->debugfs,
+ ldo_vreg, &msm_gfx_ldo_debug_info_fops);
+ if (IS_ERR_OR_NULL(temp)) {
+ pr_err("debug_info node creation failed\n");
+ return;
+ }
+
+ temp = debugfs_create_file("ldo_voltage", 0644, ldo_vreg->debugfs,
+ ldo_vreg, &ldo_voltage_fops);
+ if (IS_ERR_OR_NULL(temp)) {
+ pr_err("ldo_voltage node creation failed\n");
+ return;
+ }
+
+ temp = debugfs_create_file("ldo_mode_disable", 0644, ldo_vreg->debugfs,
+ ldo_vreg, &ldo_mode_disable_fops);
+ if (IS_ERR_OR_NULL(temp)) {
+ pr_err("ldo_mode_disable node creation failed\n");
+ return;
+ }
+}
+
+static void msm_gfx_ldo_debugfs_remove(struct msm_gfx_ldo *ldo_vreg)
+{
+ debugfs_remove_recursive(ldo_vreg->debugfs);
+}
+
+static int msm_gfx_ldo_corner_config_init(struct msm_gfx_ldo *ldo_vreg,
+ struct platform_device *pdev)
+{
+ int rc;
+
+ rc = msm_gfx_ldo_target_init(ldo_vreg);
+ if (rc) {
+ pr_err("Unable to initialize target specific data rc=%d", rc);
+ return rc;
+ }
+
+ rc = msm_gfx_ldo_parse_dt(ldo_vreg);
+ if (rc) {
+ pr_err("Unable to pasrse dt rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = msm_gfx_ldo_efuse_init(pdev, ldo_vreg);
+ if (rc) {
+ pr_err("efuse_init failed rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = msm_gfx_ldo_voltage_init(ldo_vreg);
+ if (rc) {
+ pr_err("ldo_voltage_init failed rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = msm_gfx_ldo_mem_acc_init(ldo_vreg);
+ if (rc) {
+ pr_err("Unable to initialize mem_acc rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+};
+
+/* Data corresponds to the SoC revision */
+static const struct of_device_id msm_gfx_ldo_match_table[] = {
+ {
+ .compatible = "qcom,msm8953-gfx-ldo",
+ .data = (void *)(uintptr_t)MSM8953_SOC_ID,
+ },
+ {
+ .compatible = "qcom,sdm660-gfx-ldo",
+ .data = (void *)(uintptr_t)SDM660_SOC_ID,
+ },
+ {}
+};
+
+static int msm_gfx_ldo_probe(struct platform_device *pdev)
+{
+ struct msm_gfx_ldo *ldo_vreg;
+ struct regulator_config reg_config = {};
+ struct regulator_desc *rdesc;
+ struct regulator_init_data *init_data = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ int soc_id, rc;
+
+ match = of_match_device(msm_gfx_ldo_match_table, dev);
+ if (!match)
+ return -ENODEV;
+
+ ldo_vreg = devm_kzalloc(dev, sizeof(*ldo_vreg), GFP_KERNEL);
+ if (!ldo_vreg)
+ return -ENOMEM;
+
+ init_data = of_get_regulator_init_data(dev, dev->of_node, NULL);
+ if (!init_data) {
+ pr_err("regulator init data is missing\n");
+ return -EINVAL;
+ }
+
+ init_data->constraints.input_uV = init_data->constraints.max_uV;
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
+
+ ldo_vreg->rdesc.name = init_data->constraints.name;
+ if (ldo_vreg->rdesc.name == NULL) {
+ dev_err(dev, "regulator-name missing\n");
+ return -EINVAL;
+ }
+
+ soc_id = (uintptr_t)match->data;
+ ldo_vreg->dev = &pdev->dev;
+ mutex_init(&ldo_vreg->ldo_mutex);
+ platform_set_drvdata(pdev, ldo_vreg);
+
+ switch (soc_id) {
+ case MSM8953_SOC_ID:
+ ldo_vreg->ldo_init_config = msm8953_ldo_config;
+ ldo_vreg->ops_type = CORNER;
+ rc = msm_gfx_ldo_corner_config_init(ldo_vreg, pdev);
+ if (rc) {
+ pr_err("ldo corner handling initialization failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ break;
+ case SDM660_SOC_ID:
+ ldo_vreg->ldo_init_config = sdm660_ldo_config;
+ ldo_vreg->ops_type = VOLTAGE;
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_BYPASS;
+ break;
+ default:
+ pr_err("invalid SOC ID = %d\n", soc_id);
+ return -EINVAL;
+ }
+
+ /* HW initialization */
+ rc = msm_gfx_ldo_init(pdev, ldo_vreg);
+ if (rc) {
+ pr_err("ldo_init failed rc=%d\n", rc);
+ return rc;
+ }
+
+ rdesc = &ldo_vreg->rdesc;
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+
+ if (ldo_vreg->ops_type == CORNER)
+ rdesc->ops = &msm_gfx_ldo_corner_ops;
+ else
+ rdesc->ops = &msm_gfx_ldo_voltage_ops;
+
+ reg_config.dev = &pdev->dev;
+ reg_config.init_data = init_data;
+ reg_config.driver_data = ldo_vreg;
+ reg_config.of_node = pdev->dev.of_node;
+ ldo_vreg->rdev = regulator_register(rdesc, ®_config);
+ if (IS_ERR(ldo_vreg->rdev)) {
+ rc = PTR_ERR(ldo_vreg->rdev);
+ pr_err("regulator_register failed: rc=%d\n", rc);
+ return rc;
+ }
+
+ msm_gfx_ldo_debugfs_init(ldo_vreg);
+
+ return 0;
+}
+
+static int msm_gfx_ldo_remove(struct platform_device *pdev)
+{
+ struct msm_gfx_ldo *ldo_vreg = platform_get_drvdata(pdev);
+
+ regulator_unregister(ldo_vreg->rdev);
+
+ msm_gfx_ldo_debugfs_remove(ldo_vreg);
+
+ return 0;
+}
+
+static struct platform_driver msm_gfx_ldo_driver = {
+ .driver = {
+ .name = "qcom,msm-gfx-ldo",
+ .of_match_table = msm_gfx_ldo_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = msm_gfx_ldo_probe,
+ .remove = msm_gfx_ldo_remove,
+};
+
+static int msm_gfx_ldo_platform_init(void)
+{
+ return platform_driver_register(&msm_gfx_ldo_driver);
+}
+arch_initcall(msm_gfx_ldo_platform_init);
+
+static void msm_gfx_ldo_platform_exit(void)
+{
+ platform_driver_unregister(&msm_gfx_ldo_driver);
+}
+module_exit(msm_gfx_ldo_platform_exit);
+
+MODULE_DESCRIPTION("MSM GFX LDO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/regulator/qpnp-regulator.c b/drivers/regulator/qpnp-regulator.c
new file mode 100644
index 0000000..bd70665
--- /dev/null
+++ b/drivers/regulator/qpnp-regulator.c
@@ -0,0 +1,2436 @@
+/*
+ * Copyright (c) 2012-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/ktime.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/qpnp-regulator.h>
+
+/* Debug Flag Definitions */
+enum {
+ QPNP_VREG_DEBUG_REQUEST = BIT(0), /* Show requests */
+ QPNP_VREG_DEBUG_DUPLICATE = BIT(1), /* Show duplicate requests */
+ QPNP_VREG_DEBUG_INIT = BIT(2), /* Show state after probe */
+ QPNP_VREG_DEBUG_WRITES = BIT(3), /* Show SPMI writes */
+ QPNP_VREG_DEBUG_READS = BIT(4), /* Show SPMI reads */
+ QPNP_VREG_DEBUG_OCP = BIT(5), /* Show VS OCP IRQ events */
+};
+
+static int qpnp_vreg_debug_mask;
+module_param_named(
+ debug_mask, qpnp_vreg_debug_mask, int, 0600
+);
+
+#define vreg_err(vreg, fmt, ...) \
+ pr_err("%s: " fmt, vreg->rdesc.name, ##__VA_ARGS__)
+
+/* These types correspond to unique register layouts. */
+enum qpnp_regulator_logical_type {
+ QPNP_REGULATOR_LOGICAL_TYPE_SMPS,
+ QPNP_REGULATOR_LOGICAL_TYPE_LDO,
+ QPNP_REGULATOR_LOGICAL_TYPE_VS,
+ QPNP_REGULATOR_LOGICAL_TYPE_BOOST,
+ QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS,
+ QPNP_REGULATOR_LOGICAL_TYPE_BOOST_BYP,
+ QPNP_REGULATOR_LOGICAL_TYPE_LN_LDO,
+ QPNP_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS,
+ QPNP_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS,
+ QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO,
+ QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS2,
+};
+
+enum qpnp_regulator_type {
+ QPNP_REGULATOR_TYPE_BUCK = 0x03,
+ QPNP_REGULATOR_TYPE_LDO = 0x04,
+ QPNP_REGULATOR_TYPE_VS = 0x05,
+ QPNP_REGULATOR_TYPE_BOOST = 0x1B,
+ QPNP_REGULATOR_TYPE_FTS = 0x1C,
+ QPNP_REGULATOR_TYPE_BOOST_BYP = 0x1F,
+ QPNP_REGULATOR_TYPE_ULT_LDO = 0x21,
+ QPNP_REGULATOR_TYPE_ULT_BUCK = 0x22,
+};
+
+enum qpnp_regulator_subtype {
+ QPNP_REGULATOR_SUBTYPE_GP_CTL = 0x08,
+ QPNP_REGULATOR_SUBTYPE_RF_CTL = 0x09,
+ QPNP_REGULATOR_SUBTYPE_N50 = 0x01,
+ QPNP_REGULATOR_SUBTYPE_N150 = 0x02,
+ QPNP_REGULATOR_SUBTYPE_N300 = 0x03,
+ QPNP_REGULATOR_SUBTYPE_N600 = 0x04,
+ QPNP_REGULATOR_SUBTYPE_N1200 = 0x05,
+ QPNP_REGULATOR_SUBTYPE_N600_ST = 0x06,
+ QPNP_REGULATOR_SUBTYPE_N1200_ST = 0x07,
+ QPNP_REGULATOR_SUBTYPE_N300_ST = 0x15,
+ QPNP_REGULATOR_SUBTYPE_P50 = 0x08,
+ QPNP_REGULATOR_SUBTYPE_P150 = 0x09,
+ QPNP_REGULATOR_SUBTYPE_P300 = 0x0A,
+ QPNP_REGULATOR_SUBTYPE_P600 = 0x0B,
+ QPNP_REGULATOR_SUBTYPE_P1200 = 0x0C,
+ QPNP_REGULATOR_SUBTYPE_LN = 0x10,
+ QPNP_REGULATOR_SUBTYPE_LV_P50 = 0x28,
+ QPNP_REGULATOR_SUBTYPE_LV_P150 = 0x29,
+ QPNP_REGULATOR_SUBTYPE_LV_P300 = 0x2A,
+ QPNP_REGULATOR_SUBTYPE_LV_P600 = 0x2B,
+ QPNP_REGULATOR_SUBTYPE_LV_P1200 = 0x2C,
+ QPNP_REGULATOR_SUBTYPE_LV100 = 0x01,
+ QPNP_REGULATOR_SUBTYPE_LV300 = 0x02,
+ QPNP_REGULATOR_SUBTYPE_MV300 = 0x08,
+ QPNP_REGULATOR_SUBTYPE_MV500 = 0x09,
+ QPNP_REGULATOR_SUBTYPE_HDMI = 0x10,
+ QPNP_REGULATOR_SUBTYPE_OTG = 0x11,
+ QPNP_REGULATOR_SUBTYPE_5V_BOOST = 0x01,
+ QPNP_REGULATOR_SUBTYPE_FTS_CTL = 0x08,
+ QPNP_REGULATOR_SUBTYPE_FTS2p5_CTL = 0x09,
+ QPNP_REGULATOR_SUBTYPE_FTS426 = 0x0A,
+ QPNP_REGULATOR_SUBTYPE_BB_2A = 0x01,
+ QPNP_REGULATOR_SUBTYPE_ULT_HF_CTL1 = 0x0D,
+ QPNP_REGULATOR_SUBTYPE_ULT_HF_CTL2 = 0x0E,
+ QPNP_REGULATOR_SUBTYPE_ULT_HF_CTL3 = 0x0F,
+ QPNP_REGULATOR_SUBTYPE_ULT_HF_CTL4 = 0x10,
+};
+
+/* First common register layout used by older devices */
+enum qpnp_common_regulator_registers {
+ QPNP_COMMON_REG_DIG_MAJOR_REV = 0x01,
+ QPNP_COMMON_REG_TYPE = 0x04,
+ QPNP_COMMON_REG_SUBTYPE = 0x05,
+ QPNP_COMMON_REG_VOLTAGE_RANGE = 0x40,
+ QPNP_COMMON_REG_VOLTAGE_SET = 0x41,
+ QPNP_COMMON_REG_MODE = 0x45,
+ QPNP_COMMON_REG_ENABLE = 0x46,
+ QPNP_COMMON_REG_PULL_DOWN = 0x48,
+ QPNP_COMMON_REG_STEP_CTRL = 0x61,
+};
+
+/*
+ * Second common register layout used by newer devices
+ * Note that some of the registers from the first common layout remain
+ * unchanged and their definition is not duplicated.
+ */
+enum qpnp_common2_regulator_registers {
+ QPNP_COMMON2_REG_VOLTAGE_LSB = 0x40,
+ QPNP_COMMON2_REG_VOLTAGE_MSB = 0x41,
+ QPNP_COMMON2_REG_MODE = 0x45,
+ QPNP_COMMON2_REG_STEP_CTRL = 0x61,
+};
+
+enum qpnp_ldo_registers {
+ QPNP_LDO_REG_SOFT_START = 0x4C,
+};
+
+enum qpnp_vs_registers {
+ QPNP_VS_REG_OCP = 0x4A,
+ QPNP_VS_REG_SOFT_START = 0x4C,
+};
+
+enum qpnp_boost_registers {
+ QPNP_BOOST_REG_CURRENT_LIMIT = 0x4A,
+};
+
+enum qpnp_boost_byp_registers {
+ QPNP_BOOST_BYP_REG_CURRENT_LIMIT = 0x4B,
+};
+
+/* Used for indexing into ctrl_reg. These are offets from 0x40 */
+enum qpnp_common_control_register_index {
+ QPNP_COMMON_IDX_VOLTAGE_RANGE = 0,
+ QPNP_COMMON_IDX_VOLTAGE_SET = 1,
+ QPNP_COMMON_IDX_MODE = 5,
+ QPNP_COMMON_IDX_ENABLE = 6,
+};
+
+enum qpnp_common2_control_register_index {
+ QPNP_COMMON2_IDX_VOLTAGE_LSB = 0,
+ QPNP_COMMON2_IDX_VOLTAGE_MSB = 1,
+ QPNP_COMMON2_IDX_MODE = 5,
+};
+
+/* Common regulator control register layout */
+#define QPNP_COMMON_ENABLE_MASK 0x80
+#define QPNP_COMMON_ENABLE 0x80
+#define QPNP_COMMON_DISABLE 0x00
+#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN3_MASK 0x08
+#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN2_MASK 0x04
+#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN1_MASK 0x02
+#define QPNP_COMMON_ENABLE_FOLLOW_HW_EN0_MASK 0x01
+#define QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK 0x0F
+
+/* First common regulator mode register layout */
+#define QPNP_COMMON_MODE_HPM_MASK 0x80
+#define QPNP_COMMON_MODE_AUTO_MASK 0x40
+#define QPNP_COMMON_MODE_BYPASS_MASK 0x20
+#define QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK 0x10
+#define QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK 0x08
+#define QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK 0x04
+#define QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK 0x02
+#define QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK 0x01
+#define QPNP_COMMON_MODE_FOLLOW_ALL_MASK 0x1F
+
+/* Second common regulator mode register values */
+#define QPNP_COMMON2_MODE_BYPASS 3
+#define QPNP_COMMON2_MODE_RETENTION 4
+#define QPNP_COMMON2_MODE_LPM 5
+#define QPNP_COMMON2_MODE_AUTO 6
+#define QPNP_COMMON2_MODE_HPM 7
+
+#define QPNP_COMMON2_MODE_MASK 0x07
+
+/* Common regulator pull down control register layout */
+#define QPNP_COMMON_PULL_DOWN_ENABLE_MASK 0x80
+
+/* LDO regulator current limit control register layout */
+#define QPNP_LDO_CURRENT_LIMIT_ENABLE_MASK 0x80
+
+/* LDO regulator soft start control register layout */
+#define QPNP_LDO_SOFT_START_ENABLE_MASK 0x80
+
+/* VS regulator over current protection control register layout */
+#define QPNP_VS_OCP_OVERRIDE 0x01
+#define QPNP_VS_OCP_NO_OVERRIDE 0x00
+
+/* VS regulator soft start control register layout */
+#define QPNP_VS_SOFT_START_ENABLE_MASK 0x80
+#define QPNP_VS_SOFT_START_SEL_MASK 0x03
+
+/* Boost regulator current limit control register layout */
+#define QPNP_BOOST_CURRENT_LIMIT_ENABLE_MASK 0x80
+#define QPNP_BOOST_CURRENT_LIMIT_MASK 0x07
+
+#define QPNP_VS_OCP_DEFAULT_MAX_RETRIES 10
+#define QPNP_VS_OCP_DEFAULT_RETRY_DELAY_MS 30
+#define QPNP_VS_OCP_FALL_DELAY_US 90
+#define QPNP_VS_OCP_FAULT_DELAY_US 20000
+
+#define QPNP_FTSMPS_STEP_CTRL_STEP_MASK 0x18
+#define QPNP_FTSMPS_STEP_CTRL_STEP_SHIFT 3
+#define QPNP_FTSMPS_STEP_CTRL_DELAY_MASK 0x07
+#define QPNP_FTSMPS_STEP_CTRL_DELAY_SHIFT 0
+
+/* Clock rate in kHz of the FTSMPS regulator reference clock. */
+#define QPNP_FTSMPS_CLOCK_RATE 19200
+
+/* Minimum voltage stepper delay for each step. */
+#define QPNP_FTSMPS_STEP_DELAY 8
+
+/*
+ * The ratio QPNP_FTSMPS_STEP_MARGIN_NUM/QPNP_FTSMPS_STEP_MARGIN_DEN is used to
+ * adjust the step rate in order to account for oscillator variance.
+ */
+#define QPNP_FTSMPS_STEP_MARGIN_NUM 4
+#define QPNP_FTSMPS_STEP_MARGIN_DEN 5
+
+#define QPNP_FTSMPS2_STEP_CTRL_DELAY_MASK 0x03
+#define QPNP_FTSMPS2_STEP_CTRL_DELAY_SHIFT 0
+
+/* Clock rate in kHz of the FTSMPS2 regulator reference clock. */
+#define QPNP_FTSMPS2_CLOCK_RATE 4800
+
+/* Minimum voltage stepper delay for each step. */
+#define QPNP_FTSMPS2_STEP_DELAY 2
+
+/*
+ * The ratio QPNP_FTSMPS2_STEP_MARGIN_NUM/QPNP_FTSMPS2_STEP_MARGIN_DEN is used
+ * to adjust the step rate in order to account for oscillator variance.
+ */
+#define QPNP_FTSMPS2_STEP_MARGIN_NUM 10
+#define QPNP_FTSMPS2_STEP_MARGIN_DEN 11
+
+/*
+ * This voltage in uV is returned by get_voltage functions when there is no way
+ * to determine the current voltage level. It is needed because the regulator
+ * framework treats a 0 uV voltage as an error.
+ */
+#define VOLTAGE_UNKNOWN 1
+
+/* VSET value to decide the range of ULT SMPS */
+#define ULT_SMPS_RANGE_SPLIT 0x60
+
+/**
+ * struct qpnp_voltage_range - regulator set point voltage mapping description
+ * @min_uV: Minimum programmable output voltage resulting from
+ * set point register value 0x00
+ * @max_uV: Maximum programmable output voltage
+ * @step_uV: Output voltage increase resulting from the set point
+ * register value increasing by 1
+ * @set_point_min_uV: Minimum allowed voltage
+ * @set_point_max_uV: Maximum allowed voltage. This may be tweaked in order
+ * to pick which range should be used in the case of
+ * overlapping set points.
+ * @n_voltages: Number of preferred voltage set points present in this
+ * range
+ * @range_sel: Voltage range register value corresponding to this range
+ *
+ * The following relationships must be true for the values used in this struct:
+ * (max_uV - min_uV) % step_uV == 0
+ * (set_point_min_uV - min_uV) % step_uV == 0*
+ * (set_point_max_uV - min_uV) % step_uV == 0*
+ * n_voltages = (set_point_max_uV - set_point_min_uV) / step_uV + 1
+ *
+ * *Note, set_point_min_uV == set_point_max_uV == 0 is allowed in order to
+ * specify that the voltage range has meaning, but is not preferred.
+ */
+struct qpnp_voltage_range {
+ int min_uV;
+ int max_uV;
+ int step_uV;
+ int set_point_min_uV;
+ int set_point_max_uV;
+ unsigned int n_voltages;
+ u8 range_sel;
+};
+
+/*
+ * The ranges specified in the qpnp_voltage_set_points struct must be listed
+ * so that range[i].set_point_max_uV < range[i+1].set_point_min_uV.
+ */
+struct qpnp_voltage_set_points {
+ struct qpnp_voltage_range *range;
+ int count;
+ unsigned int n_voltages;
+};
+
+struct qpnp_regulator_mapping {
+ enum qpnp_regulator_type type;
+ enum qpnp_regulator_subtype subtype;
+ enum qpnp_regulator_logical_type logical_type;
+ u32 revision_min;
+ u32 revision_max;
+ struct regulator_ops *ops;
+ struct qpnp_voltage_set_points *set_points;
+ int hpm_min_load;
+};
+
+struct qpnp_regulator {
+ struct regulator_desc rdesc;
+ struct delayed_work ocp_work;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct regulator_dev *rdev;
+ struct qpnp_voltage_set_points *set_points;
+ enum qpnp_regulator_logical_type logical_type;
+ int enable_time;
+ int ocp_enable;
+ int ocp_irq;
+ int ocp_count;
+ int ocp_max_retries;
+ int ocp_retry_delay_ms;
+ int system_load;
+ int hpm_min_load;
+ int slew_rate;
+ u32 write_count;
+ u32 prev_write_count;
+ ktime_t vs_enable_time;
+ u16 base_addr;
+ /* ctrl_reg provides a shadow copy of register values 0x40 to 0x47. */
+ u8 ctrl_reg[8];
+ u8 init_mode;
+};
+
+#define QPNP_VREG_MAP(_type, _subtype, _dig_major_min, _dig_major_max, \
+ _logical_type, _ops_val, _set_points_val, _hpm_min_load) \
+ { \
+ .type = QPNP_REGULATOR_TYPE_##_type, \
+ .subtype = QPNP_REGULATOR_SUBTYPE_##_subtype, \
+ .revision_min = _dig_major_min, \
+ .revision_max = _dig_major_max, \
+ .logical_type = QPNP_REGULATOR_LOGICAL_TYPE_##_logical_type, \
+ .ops = &qpnp_##_ops_val##_ops, \
+ .set_points = &_set_points_val##_set_points, \
+ .hpm_min_load = _hpm_min_load, \
+ }
+
+#define VOLTAGE_RANGE(_range_sel, _min_uV, _set_point_min_uV, \
+ _set_point_max_uV, _max_uV, _step_uV) \
+ { \
+ .min_uV = _min_uV, \
+ .max_uV = _max_uV, \
+ .set_point_min_uV = _set_point_min_uV, \
+ .set_point_max_uV = _set_point_max_uV, \
+ .step_uV = _step_uV, \
+ .range_sel = _range_sel, \
+ }
+
+#define SET_POINTS(_ranges) \
+{ \
+ .range = _ranges, \
+ .count = ARRAY_SIZE(_ranges), \
+}
+
+/*
+ * These tables contain the physically available PMIC regulator voltage setpoint
+ * ranges. Where two ranges overlap in hardware, one of the ranges is trimmed
+ * to ensure that the setpoints available to software are monotonically
+ * increasing and unique. The set_voltage callback functions expect these
+ * properties to hold.
+ */
+static struct qpnp_voltage_range pldo_ranges[] = {
+ VOLTAGE_RANGE(2, 750000, 750000, 1537500, 1537500, 12500),
+ VOLTAGE_RANGE(3, 1500000, 1550000, 3075000, 3075000, 25000),
+ VOLTAGE_RANGE(4, 1750000, 3100000, 4900000, 4900000, 50000),
+};
+
+static struct qpnp_voltage_range nldo1_ranges[] = {
+ VOLTAGE_RANGE(2, 750000, 750000, 1537500, 1537500, 12500),
+};
+
+static struct qpnp_voltage_range nldo2_ranges[] = {
+ VOLTAGE_RANGE(0, 375000, 0, 0, 1537500, 12500),
+ VOLTAGE_RANGE(1, 375000, 375000, 768750, 768750, 6250),
+ VOLTAGE_RANGE(2, 750000, 775000, 1537500, 1537500, 12500),
+};
+
+static struct qpnp_voltage_range nldo3_ranges[] = {
+ VOLTAGE_RANGE(0, 375000, 375000, 1537500, 1537500, 12500),
+ VOLTAGE_RANGE(1, 375000, 0, 0, 1537500, 12500),
+ VOLTAGE_RANGE(2, 750000, 0, 0, 1537500, 12500),
+};
+
+static struct qpnp_voltage_range ln_ldo_ranges[] = {
+ VOLTAGE_RANGE(1, 690000, 690000, 1110000, 1110000, 60000),
+ VOLTAGE_RANGE(0, 1380000, 1380000, 2220000, 2220000, 120000),
+};
+
+static struct qpnp_voltage_range smps_ranges[] = {
+ VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500),
+ VOLTAGE_RANGE(1, 1550000, 1575000, 3125000, 3125000, 25000),
+};
+
+static struct qpnp_voltage_range ftsmps_ranges[] = {
+ VOLTAGE_RANGE(0, 0, 350000, 1275000, 1275000, 5000),
+ VOLTAGE_RANGE(1, 0, 1280000, 2040000, 2040000, 10000),
+};
+
+static struct qpnp_voltage_range ftsmps2p5_ranges[] = {
+ VOLTAGE_RANGE(0, 80000, 350000, 1355000, 1355000, 5000),
+ VOLTAGE_RANGE(1, 160000, 1360000, 2200000, 2200000, 10000),
+};
+
+static struct qpnp_voltage_range boost_ranges[] = {
+ VOLTAGE_RANGE(0, 4000000, 4000000, 5550000, 5550000, 50000),
+};
+
+static struct qpnp_voltage_range boost_byp_ranges[] = {
+ VOLTAGE_RANGE(0, 2500000, 2500000, 5200000, 5650000, 50000),
+};
+
+static struct qpnp_voltage_range ult_lo_smps_ranges[] = {
+ VOLTAGE_RANGE(0, 375000, 375000, 1562500, 1562500, 12500),
+ VOLTAGE_RANGE(1, 750000, 0, 0, 1525000, 25000),
+};
+
+static struct qpnp_voltage_range ult_ho_smps_ranges[] = {
+ VOLTAGE_RANGE(0, 1550000, 1550000, 2325000, 2325000, 25000),
+};
+
+static struct qpnp_voltage_range ult_nldo_ranges[] = {
+ VOLTAGE_RANGE(0, 375000, 375000, 1537500, 1537500, 12500),
+};
+
+static struct qpnp_voltage_range ult_pldo_ranges[] = {
+ VOLTAGE_RANGE(0, 1750000, 1750000, 3337500, 3337500, 12500),
+};
+
+static struct qpnp_voltage_range ftsmps426_ranges[] = {
+ VOLTAGE_RANGE(0, 0, 320000, 1352000, 1352000, 4000),
+};
+
+static struct qpnp_voltage_set_points pldo_set_points = SET_POINTS(pldo_ranges);
+static struct qpnp_voltage_set_points nldo1_set_points
+ = SET_POINTS(nldo1_ranges);
+static struct qpnp_voltage_set_points nldo2_set_points
+ = SET_POINTS(nldo2_ranges);
+static struct qpnp_voltage_set_points nldo3_set_points
+ = SET_POINTS(nldo3_ranges);
+static struct qpnp_voltage_set_points ln_ldo_set_points
+ = SET_POINTS(ln_ldo_ranges);
+static struct qpnp_voltage_set_points smps_set_points = SET_POINTS(smps_ranges);
+static struct qpnp_voltage_set_points ftsmps_set_points
+ = SET_POINTS(ftsmps_ranges);
+static struct qpnp_voltage_set_points ftsmps2p5_set_points
+ = SET_POINTS(ftsmps2p5_ranges);
+static struct qpnp_voltage_set_points boost_set_points
+ = SET_POINTS(boost_ranges);
+static struct qpnp_voltage_set_points boost_byp_set_points
+ = SET_POINTS(boost_byp_ranges);
+static struct qpnp_voltage_set_points ult_lo_smps_set_points
+ = SET_POINTS(ult_lo_smps_ranges);
+static struct qpnp_voltage_set_points ult_ho_smps_set_points
+ = SET_POINTS(ult_ho_smps_ranges);
+static struct qpnp_voltage_set_points ult_nldo_set_points
+ = SET_POINTS(ult_nldo_ranges);
+static struct qpnp_voltage_set_points ult_pldo_set_points
+ = SET_POINTS(ult_pldo_ranges);
+static struct qpnp_voltage_set_points ftsmps426_set_points
+ = SET_POINTS(ftsmps426_ranges);
+static struct qpnp_voltage_set_points none_set_points;
+
+static struct qpnp_voltage_set_points *all_set_points[] = {
+ &pldo_set_points,
+ &nldo1_set_points,
+ &nldo2_set_points,
+ &nldo3_set_points,
+ &ln_ldo_set_points,
+ &smps_set_points,
+ &ftsmps_set_points,
+ &ftsmps2p5_set_points,
+ &boost_set_points,
+ &boost_byp_set_points,
+ &ult_lo_smps_set_points,
+ &ult_ho_smps_set_points,
+ &ult_nldo_set_points,
+ &ult_pldo_set_points,
+ &ftsmps426_set_points,
+};
+
+/* Determines which label to add to a debug print statement. */
+enum qpnp_regulator_action {
+ QPNP_REGULATOR_ACTION_INIT,
+ QPNP_REGULATOR_ACTION_ENABLE,
+ QPNP_REGULATOR_ACTION_DISABLE,
+ QPNP_REGULATOR_ACTION_VOLTAGE,
+ QPNP_REGULATOR_ACTION_MODE,
+};
+
+static void qpnp_vreg_show_state(struct regulator_dev *rdev,
+ enum qpnp_regulator_action action);
+
+#define DEBUG_PRINT_BUFFER_SIZE 64
+static void fill_string(char *str, size_t str_len, u8 *buf, int buf_len)
+{
+ int pos = 0;
+ int i;
+
+ for (i = 0; i < buf_len; i++) {
+ pos += scnprintf(str + pos, str_len - pos, "0x%02X", buf[i]);
+ if (i < buf_len - 1)
+ pos += scnprintf(str + pos, str_len - pos, ", ");
+ }
+}
+
+static inline int qpnp_vreg_read(struct qpnp_regulator *vreg, u16 addr, u8 *buf,
+ int len)
+{
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+ int rc = 0;
+
+ rc = regmap_bulk_read(vreg->regmap, vreg->base_addr + addr, buf, len);
+
+ if (!rc && (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_READS)) {
+ str[0] = '\0';
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buf, len);
+ pr_info(" %-11s: read(0x%04X), sid=%d, len=%d; %s\n",
+ vreg->rdesc.name, vreg->base_addr + addr,
+ to_spmi_device(vreg->pdev->dev.parent)->usid, len,
+ str);
+ }
+
+ return rc;
+}
+
+static inline int qpnp_vreg_write(struct qpnp_regulator *vreg, u16 addr,
+ u8 *buf, int len)
+{
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+ int rc = 0;
+
+ if (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_WRITES) {
+ str[0] = '\0';
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buf, len);
+ pr_info("%-11s: write(0x%04X), sid=%d, len=%d; %s\n",
+ vreg->rdesc.name, vreg->base_addr + addr,
+ to_spmi_device(vreg->pdev->dev.parent)->usid, len,
+ str);
+ }
+
+ rc = regmap_bulk_write(vreg->regmap, vreg->base_addr + addr, buf, len);
+ if (!rc)
+ vreg->write_count += len;
+
+ return rc;
+}
+
+/*
+ * qpnp_vreg_write_optimized - write the minimum sized contiguous subset of buf
+ * @vreg: qpnp_regulator pointer for this regulator
+ * @addr: local SPMI address offset from this peripheral's base address
+ * @buf: new data to write into the SPMI registers
+ * @buf_save: old data in the registers
+ * @len: number of bytes to write
+ *
+ * This function checks for unchanged register values between buf and buf_save
+ * starting at both ends of buf. Only the contiguous subset in the middle of
+ * buf starting and ending with new values is sent.
+ *
+ * Consider the following example:
+ * buf offset: 0 1 2 3 4 5 6 7
+ * reg state: U U C C U C U U
+ * (U = unchanged, C = changed)
+ * In this example registers 2 through 5 will be written with a single
+ * transaction.
+ */
+static inline int qpnp_vreg_write_optimized(struct qpnp_regulator *vreg,
+ u16 addr, u8 *buf, u8 *buf_save, int len)
+{
+ int i, rc, start, end;
+
+ for (i = 0; i < len; i++)
+ if (buf[i] != buf_save[i])
+ break;
+ start = i;
+
+ for (i = len - 1; i >= 0; i--)
+ if (buf[i] != buf_save[i])
+ break;
+ end = i;
+
+ if (start > end) {
+ /* No modified register values present. */
+ return 0;
+ }
+
+ rc = qpnp_vreg_write(vreg, addr + start, &buf[start], end - start + 1);
+ if (!rc)
+ for (i = start; i <= end; i++)
+ buf_save[i] = buf[i];
+
+ return rc;
+}
+
+/*
+ * Perform a masked write to a PMIC register only if the new value differs
+ * from the last value written to the register. This removes redundant
+ * register writing.
+ */
+static int qpnp_vreg_masked_write(struct qpnp_regulator *vreg, u16 addr, u8 val,
+ u8 mask, u8 *reg_save)
+{
+ int rc = 0;
+ u8 reg;
+
+ reg = (*reg_save & ~mask) | (val & mask);
+ if (reg != *reg_save) {
+ rc = qpnp_vreg_write(vreg, addr, ®, 1);
+
+ if (rc) {
+ vreg_err(vreg, "write failed; addr=0x%03X, rc=%d\n",
+ addr, rc);
+ } else {
+ *reg_save = reg;
+ }
+ }
+
+ return rc;
+}
+
+/*
+ * Perform a masked read-modify-write to a PMIC register only if the new value
+ * differs from the value currently in the register. This removes redundant
+ * register writing.
+ */
+static int qpnp_vreg_masked_read_write(struct qpnp_regulator *vreg, u16 addr,
+ u8 val, u8 mask)
+{
+ int rc;
+ u8 reg;
+
+ rc = qpnp_vreg_read(vreg, addr, ®, 1);
+ if (rc) {
+ vreg_err(vreg, "read failed; addr=0x%03X, rc=%d\n", addr, rc);
+ return rc;
+ }
+
+ return qpnp_vreg_masked_write(vreg, addr, val, mask, ®);
+}
+
+static int qpnp_regulator_common_is_enabled(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+
+ return (vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]
+ & QPNP_COMMON_ENABLE_MASK)
+ == QPNP_COMMON_ENABLE;
+}
+
+static int qpnp_regulator_common_enable(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc;
+
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE,
+ QPNP_COMMON_ENABLE, QPNP_COMMON_ENABLE_MASK,
+ &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]);
+
+ if (rc)
+ vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc);
+ else
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_ENABLE);
+
+ return rc;
+}
+
+static int qpnp_regulator_vs_enable(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+
+ if (vreg->ocp_irq) {
+ vreg->ocp_count = 0;
+ vreg->vs_enable_time = ktime_get();
+ }
+
+ return qpnp_regulator_common_enable(rdev);
+}
+
+static int qpnp_regulator_common_disable(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc;
+
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE,
+ QPNP_COMMON_DISABLE, QPNP_COMMON_ENABLE_MASK,
+ &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]);
+
+ if (rc)
+ vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc);
+ else
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_DISABLE);
+
+ return rc;
+}
+
+/*
+ * Returns 1 if the voltage can be set in the current range, 0 if the voltage
+ * cannot be set in the current range, or errno if an error occurred.
+ */
+static int qpnp_regulator_select_voltage_same_range(struct qpnp_regulator *vreg,
+ int min_uV, int max_uV, int *range_sel, int *voltage_sel,
+ unsigned int *selector)
+{
+ struct qpnp_voltage_range *range = NULL;
+ int uV = min_uV;
+ int i;
+
+ *range_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE];
+
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (vreg->set_points->range[i].range_sel == *range_sel) {
+ range = &vreg->set_points->range[i];
+ break;
+ }
+ }
+
+ if (!range) {
+ /* Unknown range */
+ return 0;
+ }
+
+ if (uV < range->min_uV && max_uV >= range->min_uV)
+ uV = range->min_uV;
+
+ if (uV < range->min_uV || uV > range->max_uV) {
+ /* Current range doesn't support the requested voltage. */
+ return 0;
+ }
+
+ /*
+ * Force uV to be an allowed set point by applying a ceiling function to
+ * the uV value.
+ */
+ *voltage_sel = DIV_ROUND_UP(uV - range->min_uV, range->step_uV);
+ uV = *voltage_sel * range->step_uV + range->min_uV;
+
+ if (uV > max_uV) {
+ /*
+ * No set point in the current voltage range is within the
+ * requested min_uV to max_uV range.
+ */
+ return 0;
+ }
+
+ *selector = 0;
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (uV >= vreg->set_points->range[i].set_point_min_uV
+ && uV <= vreg->set_points->range[i].set_point_max_uV) {
+ *selector +=
+ (uV - vreg->set_points->range[i].set_point_min_uV)
+ / vreg->set_points->range[i].step_uV;
+ break;
+ }
+
+ *selector += vreg->set_points->range[i].n_voltages;
+ }
+
+ if (*selector >= vreg->set_points->n_voltages)
+ return 0;
+
+ return 1;
+}
+
+static int qpnp_regulator_select_voltage(struct qpnp_regulator *vreg,
+ int min_uV, int max_uV, int *range_sel, int *voltage_sel,
+ unsigned int *selector)
+{
+ struct qpnp_voltage_range *range;
+ int uV = min_uV;
+ int lim_min_uV, lim_max_uV, i, range_id, range_max_uV;
+
+ /* Check if request voltage is outside of physically settable range. */
+ lim_min_uV = vreg->set_points->range[0].set_point_min_uV;
+ lim_max_uV =
+ vreg->set_points->range[vreg->set_points->count - 1].set_point_max_uV;
+
+ if (uV < lim_min_uV && max_uV >= lim_min_uV)
+ uV = lim_min_uV;
+
+ if (uV < lim_min_uV || uV > lim_max_uV) {
+ vreg_err(vreg,
+ "request v=[%d, %d] is outside possible v=[%d, %d]\n",
+ min_uV, max_uV, lim_min_uV, lim_max_uV);
+ return -EINVAL;
+ }
+
+ /* Find the range which uV is inside of. */
+ for (i = vreg->set_points->count - 1; i > 0; i--) {
+ range_max_uV = vreg->set_points->range[i - 1].set_point_max_uV;
+ if (uV > range_max_uV && range_max_uV > 0)
+ break;
+ }
+
+ range_id = i;
+ range = &vreg->set_points->range[range_id];
+ *range_sel = range->range_sel;
+
+ /*
+ * Force uV to be an allowed set point by applying a ceiling function to
+ * the uV value.
+ */
+ *voltage_sel = (uV - range->min_uV + range->step_uV - 1)
+ / range->step_uV;
+ uV = *voltage_sel * range->step_uV + range->min_uV;
+
+ if (uV > max_uV) {
+ vreg_err(vreg,
+ "request v=[%d, %d] cannot be met by any set point; "
+ "next set point: %d\n",
+ min_uV, max_uV, uV);
+ return -EINVAL;
+ }
+
+ *selector = 0;
+ for (i = 0; i < range_id; i++)
+ *selector += vreg->set_points->range[i].n_voltages;
+ *selector += (uV - range->set_point_min_uV) / range->step_uV;
+
+ return 0;
+}
+
+static int qpnp_regulator_delay_for_slewing(struct qpnp_regulator *vreg,
+ int prev_voltage)
+{
+ int current_voltage;
+
+ /* Delay for voltage slewing if a step rate is specified. */
+ if (vreg->slew_rate && vreg->rdesc.ops->get_voltage) {
+ current_voltage = vreg->rdesc.ops->get_voltage(vreg->rdev);
+ if (current_voltage < 0) {
+ vreg_err(vreg, "could not get new voltage, rc=%d\n",
+ current_voltage);
+ return current_voltage;
+ }
+
+ udelay(DIV_ROUND_UP(abs(current_voltage - prev_voltage),
+ vreg->slew_rate));
+ }
+
+ return 0;
+}
+
+static int qpnp_regulator_common_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc, range_sel, voltage_sel, voltage_old = 0;
+ u8 buf[2];
+
+ if (vreg->slew_rate && vreg->rdesc.ops->get_voltage) {
+ voltage_old = vreg->rdesc.ops->get_voltage(rdev);
+ if (voltage_old < 0) {
+ vreg_err(vreg, "could not get current voltage, rc=%d\n",
+ voltage_old);
+ return voltage_old;
+ }
+ }
+
+ /*
+ * Favor staying in the current voltage range if possible. This avoids
+ * voltage spikes that occur when changing the voltage range.
+ */
+ rc = qpnp_regulator_select_voltage_same_range(vreg, min_uV, max_uV,
+ &range_sel, &voltage_sel, selector);
+ if (rc == 0)
+ rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV,
+ &range_sel, &voltage_sel, selector);
+ if (rc < 0) {
+ vreg_err(vreg, "could not set voltage, rc=%d\n", rc);
+ return rc;
+ }
+
+ buf[0] = range_sel;
+ buf[1] = voltage_sel;
+ if ((vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] != range_sel)
+ && (vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET] == voltage_sel)) {
+ /* Handle latched range change. */
+ rc = qpnp_vreg_write(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE,
+ buf, 2);
+ if (!rc) {
+ vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] = buf[0];
+ vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET] = buf[1];
+ }
+ } else {
+ /* Either write can be optimized away safely. */
+ rc = qpnp_vreg_write_optimized(vreg,
+ QPNP_COMMON_REG_VOLTAGE_RANGE, buf,
+ &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE], 2);
+ }
+
+ if (rc) {
+ vreg_err(vreg, "SPMI write failed, rc=%d\n", rc);
+ } else {
+ rc = qpnp_regulator_delay_for_slewing(vreg, voltage_old);
+ if (rc)
+ return rc;
+
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE);
+ }
+
+ return rc;
+}
+
+static int qpnp_regulator_common_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ struct qpnp_voltage_range *range = NULL;
+ int range_sel, voltage_sel, i;
+
+ range_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE];
+ voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET];
+
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (vreg->set_points->range[i].range_sel == range_sel) {
+ range = &vreg->set_points->range[i];
+ break;
+ }
+ }
+
+ if (!range) {
+ vreg_err(vreg, "voltage unknown, range %d is invalid\n",
+ range_sel);
+ return VOLTAGE_UNKNOWN;
+ }
+
+ return range->step_uV * voltage_sel + range->min_uV;
+}
+
+static int qpnp_regulator_single_range_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc, range_sel, voltage_sel;
+
+ rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel,
+ &voltage_sel, selector);
+ if (rc) {
+ vreg_err(vreg, "could not set voltage, rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Certain types of regulators do not have a range select register so
+ * only voltage set register needs to be written.
+ */
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_VOLTAGE_SET,
+ voltage_sel, 0xFF, &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]);
+
+ if (rc)
+ vreg_err(vreg, "SPMI write failed, rc=%d\n", rc);
+ else
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE);
+
+ return rc;
+}
+
+static int qpnp_regulator_single_range_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ struct qpnp_voltage_range *range = &vreg->set_points->range[0];
+ int voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET];
+
+ return range->step_uV * voltage_sel + range->min_uV;
+}
+
+static int qpnp_regulator_ult_lo_smps_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc, range_sel, voltage_sel;
+
+ /*
+ * Favor staying in the current voltage range if possible. This avoids
+ * voltage spikes that occur when changing the voltage range.
+ */
+ rc = qpnp_regulator_select_voltage_same_range(vreg, min_uV, max_uV,
+ &range_sel, &voltage_sel, selector);
+ if (rc == 0)
+ rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV,
+ &range_sel, &voltage_sel, selector);
+ if (rc < 0) {
+ vreg_err(vreg, "could not set voltage, rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Calculate VSET based on range
+ * In case of range 0: voltage_sel is a 7 bit value, can be written
+ * witout any modification.
+ * In case of range 1: voltage_sel is a 5 bit value, bits[7-5] set to
+ * [011].
+ */
+ if (range_sel == 1)
+ voltage_sel |= ULT_SMPS_RANGE_SPLIT;
+
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_VOLTAGE_SET,
+ voltage_sel, 0xFF, &vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]);
+ if (rc) {
+ vreg_err(vreg, "SPMI write failed, rc=%d\n", rc);
+ } else {
+ vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] = range_sel;
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE);
+ }
+
+ return rc;
+}
+
+static int qpnp_regulator_ult_lo_smps_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ struct qpnp_voltage_range *range = NULL;
+ int range_sel, voltage_sel, i;
+
+ range_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE];
+ voltage_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET];
+
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (vreg->set_points->range[i].range_sel == range_sel) {
+ range = &vreg->set_points->range[i];
+ break;
+ }
+ }
+
+ if (!range) {
+ vreg_err(vreg, "voltage unknown, range %d is invalid\n",
+ range_sel);
+ return VOLTAGE_UNKNOWN;
+ }
+
+ if (range_sel == 1)
+ voltage_sel &= ~ULT_SMPS_RANGE_SPLIT;
+
+ return range->step_uV * voltage_sel + range->min_uV;
+}
+
+static int qpnp_regulator_common_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int uV = 0;
+ int i;
+
+ if (selector >= vreg->set_points->n_voltages)
+ return 0;
+
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (selector < vreg->set_points->range[i].n_voltages) {
+ uV = selector * vreg->set_points->range[i].step_uV
+ + vreg->set_points->range[i].set_point_min_uV;
+ break;
+ }
+
+ selector -= vreg->set_points->range[i].n_voltages;
+ }
+
+ return uV;
+}
+
+static int qpnp_regulator_common2_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV, unsigned int *selector)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc, range_sel, voltage_sel, voltage_old = 0;
+ int voltage_uV, voltage_mV;
+ u8 buf[2];
+
+ if (vreg->slew_rate && vreg->rdesc.ops->get_voltage) {
+ voltage_old = vreg->rdesc.ops->get_voltage(rdev);
+ if (voltage_old < 0) {
+ vreg_err(vreg, "could not get current voltage, rc=%d\n",
+ voltage_old);
+ return voltage_old;
+ }
+ }
+
+ rc = qpnp_regulator_select_voltage(vreg, min_uV, max_uV, &range_sel,
+ &voltage_sel, selector);
+ if (rc < 0) {
+ vreg_err(vreg, "could not set voltage, rc=%d\n", rc);
+ return rc;
+ }
+
+ voltage_uV = qpnp_regulator_common_list_voltage(rdev, *selector);
+ voltage_mV = voltage_uV / 1000;
+ buf[0] = voltage_mV & 0xFF;
+ buf[1] = (voltage_mV >> 8) & 0xFF;
+
+ if (vreg->ctrl_reg[QPNP_COMMON2_IDX_VOLTAGE_LSB] != buf[0]
+ || vreg->ctrl_reg[QPNP_COMMON2_IDX_VOLTAGE_MSB] != buf[1]) {
+ /* MSB must always be written even if it is unchanged. */
+ rc = qpnp_vreg_write(vreg, QPNP_COMMON2_REG_VOLTAGE_LSB,
+ buf, 2);
+ if (rc) {
+ vreg_err(vreg, "SPMI write failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ vreg->ctrl_reg[QPNP_COMMON2_IDX_VOLTAGE_LSB] = buf[0];
+ vreg->ctrl_reg[QPNP_COMMON2_IDX_VOLTAGE_MSB] = buf[1];
+
+ rc = qpnp_regulator_delay_for_slewing(vreg, voltage_old);
+ if (rc)
+ return rc;
+
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_VOLTAGE);
+ }
+
+ return rc;
+}
+
+static int qpnp_regulator_common2_get_voltage(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+
+ return (((int)vreg->ctrl_reg[QPNP_COMMON2_IDX_VOLTAGE_MSB] << 8)
+ | (int)vreg->ctrl_reg[QPNP_COMMON2_IDX_VOLTAGE_LSB]) * 1000;
+}
+
+static unsigned int qpnp_regulator_common_get_mode(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+
+ return (vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]
+ & QPNP_COMMON_MODE_HPM_MASK)
+ ? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE;
+}
+
+static int qpnp_regulator_common_set_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc = 0;
+ u8 val;
+
+ if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) {
+ vreg_err(vreg, "invalid mode: %u\n", mode);
+ return -EINVAL;
+ }
+
+ val = (mode == REGULATOR_MODE_NORMAL ? QPNP_COMMON_MODE_HPM_MASK : 0);
+
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_MODE, val,
+ QPNP_COMMON_MODE_HPM_MASK,
+ &vreg->ctrl_reg[QPNP_COMMON_IDX_MODE]);
+
+ if (rc)
+ vreg_err(vreg, "SPMI write failed, rc=%d\n", rc);
+ else
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_MODE);
+
+ return rc;
+}
+
+static unsigned int qpnp_regulator_common_get_optimum_mode(
+ struct regulator_dev *rdev, int input_uV, int output_uV,
+ int load_uA)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ unsigned int mode;
+
+ if (load_uA + vreg->system_load >= vreg->hpm_min_load)
+ mode = REGULATOR_MODE_NORMAL;
+ else
+ mode = REGULATOR_MODE_IDLE;
+
+ return mode;
+}
+
+static unsigned int qpnp_regulator_common2_get_mode(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+
+ return vreg->ctrl_reg[QPNP_COMMON2_IDX_MODE] == QPNP_COMMON2_MODE_HPM
+ ? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE;
+}
+
+static int qpnp_regulator_common2_set_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ int rc = 0;
+ u8 val = QPNP_COMMON2_MODE_HPM;
+
+ if (mode != REGULATOR_MODE_NORMAL && mode != REGULATOR_MODE_IDLE) {
+ vreg_err(vreg, "invalid mode: %u\n", mode);
+ return -EINVAL;
+ }
+
+ /*
+ * Use init_mode as the low power mode unless it is equal to HPM. This
+ * ensures that AUTO mode is re-asserted after switching away from
+ * forced HPM if it was configured initially.
+ */
+ if (mode == REGULATOR_MODE_NORMAL)
+ val = QPNP_COMMON2_MODE_HPM;
+ else if (vreg->init_mode == QPNP_COMMON2_MODE_HPM)
+ val = QPNP_COMMON2_MODE_LPM;
+ else
+ val = vreg->init_mode;
+
+ rc = qpnp_vreg_write_optimized(vreg, QPNP_COMMON2_REG_MODE, &val,
+ &vreg->ctrl_reg[QPNP_COMMON2_IDX_MODE], 1);
+ if (rc)
+ vreg_err(vreg, "SPMI write failed, rc=%d\n", rc);
+ else
+ qpnp_vreg_show_state(rdev, QPNP_REGULATOR_ACTION_MODE);
+
+ return rc;
+}
+
+static int qpnp_regulator_common_enable_time(struct regulator_dev *rdev)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+
+ return vreg->enable_time;
+}
+
+static int qpnp_regulator_vs_clear_ocp(struct qpnp_regulator *vreg)
+{
+ int rc;
+
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE,
+ QPNP_COMMON_DISABLE, QPNP_COMMON_ENABLE_MASK,
+ &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]);
+ if (rc)
+ vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc);
+
+ vreg->vs_enable_time = ktime_get();
+
+ rc = qpnp_vreg_masked_write(vreg, QPNP_COMMON_REG_ENABLE,
+ QPNP_COMMON_ENABLE, QPNP_COMMON_ENABLE_MASK,
+ &vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE]);
+ if (rc)
+ vreg_err(vreg, "qpnp_vreg_masked_write failed, rc=%d\n", rc);
+
+ if (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_OCP) {
+ pr_info("%s: switch state toggled after OCP event\n",
+ vreg->rdesc.name);
+ }
+
+ return rc;
+}
+
+static void qpnp_regulator_vs_ocp_work(struct work_struct *work)
+{
+ struct delayed_work *dwork
+ = container_of(work, struct delayed_work, work);
+ struct qpnp_regulator *vreg
+ = container_of(dwork, struct qpnp_regulator, ocp_work);
+
+ qpnp_regulator_vs_clear_ocp(vreg);
+}
+
+static irqreturn_t qpnp_regulator_vs_ocp_isr(int irq, void *data)
+{
+ struct qpnp_regulator *vreg = data;
+ ktime_t ocp_irq_time;
+ s64 ocp_trigger_delay_us;
+
+ ocp_irq_time = ktime_get();
+ ocp_trigger_delay_us = ktime_us_delta(ocp_irq_time,
+ vreg->vs_enable_time);
+
+ /*
+ * Reset the OCP count if there is a large delay between switch enable
+ * and when OCP triggers. This is indicative of a hotplug event as
+ * opposed to a fault.
+ */
+ if (ocp_trigger_delay_us > QPNP_VS_OCP_FAULT_DELAY_US)
+ vreg->ocp_count = 0;
+
+ /* Wait for switch output to settle back to 0 V after OCP triggered. */
+ udelay(QPNP_VS_OCP_FALL_DELAY_US);
+
+ vreg->ocp_count++;
+
+ if (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_OCP) {
+ pr_info("%s: VS OCP triggered, count = %d, delay = %lld us\n",
+ vreg->rdesc.name, vreg->ocp_count,
+ ocp_trigger_delay_us);
+ }
+
+ if (vreg->ocp_count == 1) {
+ /* Immediately clear the over current condition. */
+ qpnp_regulator_vs_clear_ocp(vreg);
+ } else if (vreg->ocp_count <= vreg->ocp_max_retries) {
+ /* Schedule the over current clear task to run later. */
+ schedule_delayed_work(&vreg->ocp_work,
+ msecs_to_jiffies(vreg->ocp_retry_delay_ms) + 1);
+ } else {
+ vreg_err(vreg, "OCP triggered %d times; no further retries\n",
+ vreg->ocp_count);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static const char * const qpnp_print_actions[] = {
+ [QPNP_REGULATOR_ACTION_INIT] = "initial ",
+ [QPNP_REGULATOR_ACTION_ENABLE] = "enable ",
+ [QPNP_REGULATOR_ACTION_DISABLE] = "disable ",
+ [QPNP_REGULATOR_ACTION_VOLTAGE] = "set voltage",
+ [QPNP_REGULATOR_ACTION_MODE] = "set mode ",
+};
+
+static const char * const qpnp_common2_mode_label[] = {
+ [0] = "RSV",
+ [1] = "RSV",
+ [2] = "RSV",
+ [QPNP_COMMON2_MODE_BYPASS] = "BYP",
+ [QPNP_COMMON2_MODE_RETENTION] = "RET",
+ [QPNP_COMMON2_MODE_LPM] = "LPM",
+ [QPNP_COMMON2_MODE_AUTO] = "AUTO",
+ [QPNP_COMMON2_MODE_HPM] = "HPM",
+};
+
+static void qpnp_vreg_show_state(struct regulator_dev *rdev,
+ enum qpnp_regulator_action action)
+{
+ struct qpnp_regulator *vreg = rdev_get_drvdata(rdev);
+ const char *action_label = qpnp_print_actions[action];
+ unsigned int mode = 0;
+ int uV = 0;
+ const char *mode_label = "";
+ enum qpnp_regulator_logical_type type;
+ const char *enable_label = "";
+ char pc_enable_label[5] = {'\0'};
+ char pc_mode_label[8] = {'\0'};
+ bool show_req, show_dupe, show_init, has_changed;
+ u8 en_reg, mode_reg;
+
+ /* Do not print unless appropriate flags are set. */
+ show_req = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_REQUEST;
+ show_dupe = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_DUPLICATE;
+ show_init = qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_INIT;
+ has_changed = vreg->write_count != vreg->prev_write_count;
+ if (!((show_init && action == QPNP_REGULATOR_ACTION_INIT)
+ || (show_req && (has_changed || show_dupe)))) {
+ return;
+ }
+
+ vreg->prev_write_count = vreg->write_count;
+
+ type = vreg->logical_type;
+
+ if (vreg->rdesc.ops->is_enabled)
+ enable_label = vreg->rdesc.ops->is_enabled(rdev)
+ ? "on " : "off";
+
+ if (vreg->rdesc.ops->get_voltage)
+ uV = vreg->rdesc.ops->get_voltage(rdev);
+
+ if (vreg->rdesc.ops->get_mode) {
+ mode = vreg->rdesc.ops->get_mode(rdev);
+ mode_label = mode == REGULATOR_MODE_NORMAL ? "HPM" : "LPM";
+ }
+
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_VS) {
+ en_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_ENABLE];
+ pc_enable_label[0] =
+ en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN3_MASK ? '3' : '_';
+ pc_enable_label[1] =
+ en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN2_MASK ? '2' : '_';
+ pc_enable_label[2] =
+ en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN1_MASK ? '1' : '_';
+ pc_enable_label[3] =
+ en_reg & QPNP_COMMON_ENABLE_FOLLOW_HW_EN0_MASK ? '0' : '_';
+ }
+
+ switch (type) {
+ case QPNP_REGULATOR_LOGICAL_TYPE_SMPS:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_';
+ pc_mode_label[1] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_';
+ pc_mode_label[2] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK ? '3' : '_';
+ pc_mode_label[3] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK ? '2' : '_';
+ pc_mode_label[4] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK ? '1' : '_';
+ pc_mode_label[5] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK ? '0' : '_';
+
+ pr_info("%s %-11s: %s, v=%7d uV, mode=%s, pc_en=%s, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ mode_label, pc_enable_label, pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_LDO:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_';
+ pc_mode_label[1] =
+ mode_reg & QPNP_COMMON_MODE_BYPASS_MASK ? 'B' : '_';
+ pc_mode_label[2] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_';
+ pc_mode_label[3] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN3_MASK ? '3' : '_';
+ pc_mode_label[4] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN2_MASK ? '2' : '_';
+ pc_mode_label[5] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN1_MASK ? '1' : '_';
+ pc_mode_label[6] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_HW_EN0_MASK ? '0' : '_';
+
+ pr_info("%s %-11s: %s, v=%7d uV, mode=%s, pc_en=%s, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ mode_label, pc_enable_label, pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_LN_LDO:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_BYPASS_MASK ? 'B' : '_';
+
+ pr_info("%s %-11s: %s, v=%7d uV, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_VS:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_';
+ pc_mode_label[1] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_';
+
+ pr_info("%s %-11s: %s, mode=%s, pc_en=%s, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label,
+ mode_label, pc_enable_label, pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_BOOST:
+ pr_info("%s %-11s: %s, v=%7d uV\n",
+ action_label, vreg->rdesc.name, enable_label, uV);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_BOOST_BYP:
+ pr_info("%s %-11s: %s, v=%7d uV\n",
+ action_label, vreg->rdesc.name, enable_label, uV);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_AUTO_MASK ? 'A' : '_';
+
+ pr_info("%s %-11s: %s, v=%7d uV, mode=%s, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ mode_label, pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS:
+ case QPNP_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_';
+ pr_info("%s %-11s: %s, v=%7d uV, mode=%s, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ mode_label, pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ pc_mode_label[0] =
+ mode_reg & QPNP_COMMON_MODE_BYPASS_MASK ? 'B' : '_';
+ pc_mode_label[1] =
+ mode_reg & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK ? 'W' : '_';
+ pr_info("%s %-11s: %s, v=%7d uV, mode=%s, alt_mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ mode_label, pc_mode_label);
+ break;
+ case QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS2:
+ mode_reg = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+ mode_label = qpnp_common2_mode_label[mode_reg
+ & QPNP_COMMON2_MODE_MASK];
+ pr_info("%s %-11s: %s, v=%7d uV, mode=%s\n",
+ action_label, vreg->rdesc.name, enable_label, uV,
+ mode_label);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct regulator_ops qpnp_smps_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_common_set_voltage,
+ .get_voltage = qpnp_regulator_common_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common_set_mode,
+ .get_mode = qpnp_regulator_common_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ldo_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_common_set_voltage,
+ .get_voltage = qpnp_regulator_common_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common_set_mode,
+ .get_mode = qpnp_regulator_common_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ln_ldo_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_common_set_voltage,
+ .get_voltage = qpnp_regulator_common_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_vs_ops = {
+ .enable = qpnp_regulator_vs_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_boost_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_single_range_set_voltage,
+ .get_voltage = qpnp_regulator_single_range_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ftsmps_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_common_set_voltage,
+ .get_voltage = qpnp_regulator_common_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common_set_mode,
+ .get_mode = qpnp_regulator_common_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ult_lo_smps_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_ult_lo_smps_set_voltage,
+ .get_voltage = qpnp_regulator_ult_lo_smps_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common_set_mode,
+ .get_mode = qpnp_regulator_common_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ult_ho_smps_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_single_range_set_voltage,
+ .get_voltage = qpnp_regulator_single_range_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common_set_mode,
+ .get_mode = qpnp_regulator_common_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ult_ldo_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_single_range_set_voltage,
+ .get_voltage = qpnp_regulator_single_range_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common_set_mode,
+ .get_mode = qpnp_regulator_common_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+static struct regulator_ops qpnp_ftsmps426_ops = {
+ .enable = qpnp_regulator_common_enable,
+ .disable = qpnp_regulator_common_disable,
+ .is_enabled = qpnp_regulator_common_is_enabled,
+ .set_voltage = qpnp_regulator_common2_set_voltage,
+ .get_voltage = qpnp_regulator_common2_get_voltage,
+ .list_voltage = qpnp_regulator_common_list_voltage,
+ .set_mode = qpnp_regulator_common2_set_mode,
+ .get_mode = qpnp_regulator_common2_get_mode,
+ .get_optimum_mode = qpnp_regulator_common_get_optimum_mode,
+ .enable_time = qpnp_regulator_common_enable_time,
+};
+
+/* Maximum possible digital major revision value */
+#define INF 0xFF
+
+static const struct qpnp_regulator_mapping supported_regulators[] = {
+ /* type subtype dig_min dig_max ltype ops setpoints hpm_min */
+ QPNP_VREG_MAP(BUCK, GP_CTL, 0, INF, SMPS, smps, smps, 100000),
+ QPNP_VREG_MAP(LDO, N300, 0, INF, LDO, ldo, nldo1, 10000),
+ QPNP_VREG_MAP(LDO, N600, 0, 0, LDO, ldo, nldo2, 10000),
+ QPNP_VREG_MAP(LDO, N1200, 0, 0, LDO, ldo, nldo2, 10000),
+ QPNP_VREG_MAP(LDO, N600, 1, INF, LDO, ldo, nldo3, 10000),
+ QPNP_VREG_MAP(LDO, N1200, 1, INF, LDO, ldo, nldo3, 10000),
+ QPNP_VREG_MAP(LDO, N600_ST, 0, 0, LDO, ldo, nldo2, 10000),
+ QPNP_VREG_MAP(LDO, N1200_ST, 0, 0, LDO, ldo, nldo2, 10000),
+ QPNP_VREG_MAP(LDO, N600_ST, 1, INF, LDO, ldo, nldo3, 10000),
+ QPNP_VREG_MAP(LDO, N1200_ST, 1, INF, LDO, ldo, nldo3, 10000),
+ QPNP_VREG_MAP(LDO, P50, 0, INF, LDO, ldo, pldo, 5000),
+ QPNP_VREG_MAP(LDO, P150, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, P300, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, P600, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, P1200, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, LN, 0, INF, LN_LDO, ln_ldo, ln_ldo, 0),
+ QPNP_VREG_MAP(LDO, LV_P50, 0, INF, LDO, ldo, pldo, 5000),
+ QPNP_VREG_MAP(LDO, LV_P150, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, LV_P300, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, LV_P600, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(LDO, LV_P1200, 0, INF, LDO, ldo, pldo, 10000),
+ QPNP_VREG_MAP(VS, LV100, 0, INF, VS, vs, none, 0),
+ QPNP_VREG_MAP(VS, LV300, 0, INF, VS, vs, none, 0),
+ QPNP_VREG_MAP(VS, MV300, 0, INF, VS, vs, none, 0),
+ QPNP_VREG_MAP(VS, MV500, 0, INF, VS, vs, none, 0),
+ QPNP_VREG_MAP(VS, HDMI, 0, INF, VS, vs, none, 0),
+ QPNP_VREG_MAP(VS, OTG, 0, INF, VS, vs, none, 0),
+ QPNP_VREG_MAP(BOOST, 5V_BOOST, 0, INF, BOOST, boost, boost, 0),
+ QPNP_VREG_MAP(FTS, FTS_CTL, 0, INF, FTSMPS, ftsmps, ftsmps, 100000),
+ QPNP_VREG_MAP(FTS, FTS2p5_CTL, 0, INF, FTSMPS, ftsmps, ftsmps2p5,
+ 100000),
+ QPNP_VREG_MAP(BOOST_BYP, BB_2A, 0, INF, BOOST_BYP, boost, boost_byp, 0),
+ QPNP_VREG_MAP(ULT_BUCK, ULT_HF_CTL1, 0, INF, ULT_LO_SMPS, ult_lo_smps,
+ ult_lo_smps, 100000),
+ QPNP_VREG_MAP(ULT_BUCK, ULT_HF_CTL2, 0, INF, ULT_LO_SMPS, ult_lo_smps,
+ ult_lo_smps, 100000),
+ QPNP_VREG_MAP(ULT_BUCK, ULT_HF_CTL3, 0, INF, ULT_LO_SMPS, ult_lo_smps,
+ ult_lo_smps, 100000),
+ QPNP_VREG_MAP(ULT_BUCK, ULT_HF_CTL4, 0, INF, ULT_HO_SMPS, ult_ho_smps,
+ ult_ho_smps, 100000),
+ QPNP_VREG_MAP(ULT_LDO, N300_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, N600_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, N1200_ST, 0, INF, ULT_LDO, ult_ldo, ult_nldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, LV_P150, 0, INF, ULT_LDO, ult_ldo, ult_pldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, LV_P300, 0, INF, ULT_LDO, ult_ldo, ult_pldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, P600, 0, INF, ULT_LDO, ult_ldo, ult_pldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, P150, 0, INF, ULT_LDO, ult_ldo, ult_pldo,
+ 10000),
+ QPNP_VREG_MAP(ULT_LDO, P50, 0, INF, ULT_LDO, ult_ldo, ult_pldo,
+ 5000),
+ QPNP_VREG_MAP(FTS, FTS426, 0, INF, FTSMPS2, ftsmps426, ftsmps426,
+ 100000),
+};
+
+static int qpnp_regulator_match(struct qpnp_regulator *vreg)
+{
+ const struct qpnp_regulator_mapping *mapping;
+ struct device_node *node = vreg->pdev->dev.of_node;
+ int rc, i;
+ u32 type_reg[2], dig_major_rev;
+ u8 version[QPNP_COMMON_REG_SUBTYPE - QPNP_COMMON_REG_DIG_MAJOR_REV + 1];
+ u8 type, subtype;
+
+ rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_DIG_MAJOR_REV, version,
+ ARRAY_SIZE(version));
+ if (rc) {
+ vreg_err(vreg, "could not read version registers, rc=%d\n", rc);
+ return rc;
+ }
+ dig_major_rev = version[QPNP_COMMON_REG_DIG_MAJOR_REV
+ - QPNP_COMMON_REG_DIG_MAJOR_REV];
+ type = version[QPNP_COMMON_REG_TYPE
+ - QPNP_COMMON_REG_DIG_MAJOR_REV];
+ subtype = version[QPNP_COMMON_REG_SUBTYPE
+ - QPNP_COMMON_REG_DIG_MAJOR_REV];
+
+ /*
+ * Override type and subtype register values if qcom,force-type is
+ * present in the device tree node.
+ */
+ rc = of_property_read_u32_array(node, "qcom,force-type", type_reg, 2);
+ if (!rc) {
+ type = type_reg[0];
+ subtype = type_reg[1];
+ }
+
+ rc = -ENODEV;
+ for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) {
+ mapping = &supported_regulators[i];
+ if (mapping->type == type && mapping->subtype == subtype
+ && mapping->revision_min <= dig_major_rev
+ && mapping->revision_max >= dig_major_rev) {
+ vreg->logical_type = mapping->logical_type;
+ vreg->set_points = mapping->set_points;
+ vreg->hpm_min_load = mapping->hpm_min_load;
+ vreg->rdesc.ops = mapping->ops;
+ vreg->rdesc.n_voltages
+ = mapping->set_points->n_voltages;
+ rc = 0;
+ break;
+ }
+ }
+
+ if (rc)
+ vreg_err(vreg, "unsupported regulator: type=0x%02X, subtype=0x%02X, dig major rev=0x%02X\n",
+ type, subtype, dig_major_rev);
+
+ return rc;
+}
+
+static int qpnp_regulator_ftsmps_init_slew_rate(struct qpnp_regulator *vreg)
+{
+ int rc;
+ u8 reg = 0;
+ int step = 0, delay, i, range_sel;
+ struct qpnp_voltage_range *range = NULL;
+
+ rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_STEP_CTRL, ®, 1);
+ if (rc) {
+ vreg_err(vreg, "spmi read failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ range_sel = vreg->ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE];
+
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (vreg->set_points->range[i].range_sel == range_sel) {
+ range = &vreg->set_points->range[i];
+ break;
+ }
+ }
+
+ if (!range) {
+ vreg_err(vreg, "range %d is invalid\n", range_sel);
+ return -EINVAL;
+ }
+
+ step = (reg & QPNP_FTSMPS_STEP_CTRL_STEP_MASK)
+ >> QPNP_FTSMPS_STEP_CTRL_STEP_SHIFT;
+
+ delay = (reg & QPNP_FTSMPS_STEP_CTRL_DELAY_MASK)
+ >> QPNP_FTSMPS_STEP_CTRL_DELAY_SHIFT;
+
+ /* slew_rate has units of uV/us. */
+ vreg->slew_rate = QPNP_FTSMPS_CLOCK_RATE * range->step_uV * (1 << step);
+
+ vreg->slew_rate /= 1000 * (QPNP_FTSMPS_STEP_DELAY << delay);
+
+ vreg->slew_rate = vreg->slew_rate * QPNP_FTSMPS_STEP_MARGIN_NUM
+ / QPNP_FTSMPS_STEP_MARGIN_DEN;
+
+ /* Ensure that the slew rate is greater than 0. */
+ vreg->slew_rate = max(vreg->slew_rate, 1);
+
+ return rc;
+}
+
+static int qpnp_regulator_ftsmps2_init_slew_rate(struct qpnp_regulator *vreg)
+{
+ struct qpnp_voltage_range *range = NULL;
+ int i, rc, delay;
+ u8 reg = 0;
+
+ rc = qpnp_vreg_read(vreg, QPNP_COMMON2_REG_STEP_CTRL, ®, 1);
+ if (rc) {
+ vreg_err(vreg, "spmi read failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Regulators using the common #2 register layout do not have a voltage
+ * range select register. Choose the lowest possible step size to be
+ * conservative in the slew rate calculation.
+ */
+ for (i = 0; i < vreg->set_points->count; i++) {
+ if (!range || vreg->set_points->range[i].step_uV
+ < range->step_uV)
+ range = &vreg->set_points->range[i];
+ }
+
+ if (!range) {
+ vreg_err(vreg, "range is invalid\n");
+ return -EINVAL;
+ }
+
+ delay = (reg & QPNP_FTSMPS2_STEP_CTRL_DELAY_MASK)
+ >> QPNP_FTSMPS2_STEP_CTRL_DELAY_SHIFT;
+
+ /* slew_rate has units of uV/us. */
+ vreg->slew_rate = QPNP_FTSMPS2_CLOCK_RATE * range->step_uV;
+ vreg->slew_rate /= 1000 * (QPNP_FTSMPS2_STEP_DELAY << delay);
+ vreg->slew_rate = vreg->slew_rate * QPNP_FTSMPS2_STEP_MARGIN_NUM
+ / QPNP_FTSMPS2_STEP_MARGIN_DEN;
+
+ /* Ensure that the slew rate is greater than 0. */
+ vreg->slew_rate = max(vreg->slew_rate, 1);
+
+ return rc;
+}
+
+static int qpnp_regulator_init_registers(struct qpnp_regulator *vreg,
+ struct qpnp_regulator_platform_data *pdata)
+{
+ int rc, i;
+ enum qpnp_regulator_logical_type type;
+ u8 ctrl_reg[8], reg, mask;
+
+ type = vreg->logical_type;
+
+ rc = qpnp_vreg_read(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE,
+ vreg->ctrl_reg, 8);
+ if (rc) {
+ vreg_err(vreg, "spmi read failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ctrl_reg); i++)
+ ctrl_reg[i] = vreg->ctrl_reg[i];
+
+ /* Set up enable pin control. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_VS)
+ && !(pdata->pin_ctrl_enable
+ & QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT)) {
+ ctrl_reg[QPNP_COMMON_IDX_ENABLE] &=
+ ~QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_ENABLE] |=
+ pdata->pin_ctrl_enable & QPNP_COMMON_ENABLE_FOLLOW_ALL_MASK;
+ }
+
+ /* Set up HPM control. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_VS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS)
+ && (pdata->hpm_enable != QPNP_REGULATOR_USE_HW_DEFAULT)) {
+ ctrl_reg[QPNP_COMMON_IDX_MODE] &= ~QPNP_COMMON_MODE_HPM_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_MODE] |=
+ (pdata->hpm_enable ? QPNP_COMMON_MODE_HPM_MASK : 0);
+ }
+
+ /* Set up auto mode control. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_VS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS)
+ && (pdata->auto_mode_enable != QPNP_REGULATOR_USE_HW_DEFAULT)) {
+ ctrl_reg[QPNP_COMMON_IDX_MODE] &=
+ ~QPNP_COMMON_MODE_AUTO_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_MODE] |=
+ (pdata->auto_mode_enable ? QPNP_COMMON_MODE_AUTO_MASK : 0);
+ }
+
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS2) {
+ if (pdata->hpm_enable == QPNP_REGULATOR_ENABLE)
+ ctrl_reg[QPNP_COMMON2_IDX_MODE]
+ = QPNP_COMMON2_MODE_HPM;
+ else if (pdata->auto_mode_enable == QPNP_REGULATOR_ENABLE)
+ ctrl_reg[QPNP_COMMON2_IDX_MODE]
+ = QPNP_COMMON2_MODE_AUTO;
+ else if (pdata->hpm_enable == QPNP_REGULATOR_DISABLE
+ && ctrl_reg[QPNP_COMMON2_IDX_MODE]
+ == QPNP_COMMON2_MODE_HPM)
+ ctrl_reg[QPNP_COMMON2_IDX_MODE]
+ = QPNP_COMMON2_MODE_LPM;
+ else if (pdata->auto_mode_enable == QPNP_REGULATOR_DISABLE
+ && ctrl_reg[QPNP_COMMON2_IDX_MODE]
+ == QPNP_COMMON2_MODE_AUTO)
+ ctrl_reg[QPNP_COMMON2_IDX_MODE]
+ = QPNP_COMMON2_MODE_LPM;
+ }
+
+ /* Set up mode pin control. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO)
+ && !(pdata->pin_ctrl_hpm
+ & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) {
+ ctrl_reg[QPNP_COMMON_IDX_MODE] &=
+ ~QPNP_COMMON_MODE_FOLLOW_ALL_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_MODE] |=
+ pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_ALL_MASK;
+ }
+
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_VS
+ && !(pdata->pin_ctrl_hpm & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) {
+ ctrl_reg[QPNP_COMMON_IDX_MODE] &=
+ ~QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_MODE] |=
+ pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK;
+ }
+
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO)
+ && !(pdata->pin_ctrl_hpm
+ & QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT)) {
+ ctrl_reg[QPNP_COMMON_IDX_MODE] &=
+ ~QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_MODE] |=
+ pdata->pin_ctrl_hpm & QPNP_COMMON_MODE_FOLLOW_AWAKE_MASK;
+ }
+
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LN_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO)
+ && pdata->bypass_mode_enable != QPNP_REGULATOR_USE_HW_DEFAULT) {
+ ctrl_reg[QPNP_COMMON_IDX_MODE] &=
+ ~QPNP_COMMON_MODE_BYPASS_MASK;
+ ctrl_reg[QPNP_COMMON_IDX_MODE] |=
+ (pdata->bypass_mode_enable
+ ? QPNP_COMMON_MODE_BYPASS_MASK : 0);
+ }
+
+ /* Set boost current limit. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST_BYP)
+ && pdata->boost_current_limit
+ != QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT) {
+ reg = pdata->boost_current_limit;
+ mask = QPNP_BOOST_CURRENT_LIMIT_MASK;
+ rc = qpnp_vreg_masked_read_write(vreg,
+ (type == QPNP_REGULATOR_LOGICAL_TYPE_BOOST
+ ? QPNP_BOOST_REG_CURRENT_LIMIT
+ : QPNP_BOOST_BYP_REG_CURRENT_LIMIT),
+ reg, mask);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Write back any control register values that were modified. */
+ rc = qpnp_vreg_write_optimized(vreg, QPNP_COMMON_REG_VOLTAGE_RANGE,
+ ctrl_reg, vreg->ctrl_reg, 8);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Setup initial range for ULT_LO_SMPS */
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS) {
+ ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_RANGE] =
+ (ctrl_reg[QPNP_COMMON_IDX_VOLTAGE_SET]
+ < ULT_SMPS_RANGE_SPLIT) ? 0 : 1;
+ }
+
+ /* Set pull down. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LO_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_HO_SMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_VS)
+ && pdata->pull_down_enable != QPNP_REGULATOR_USE_HW_DEFAULT) {
+ reg = pdata->pull_down_enable
+ ? QPNP_COMMON_PULL_DOWN_ENABLE_MASK : 0;
+ rc = qpnp_vreg_write(vreg, QPNP_COMMON_REG_PULL_DOWN, ®, 1);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS2)
+ && pdata->pull_down_enable != QPNP_REGULATOR_USE_HW_DEFAULT) {
+ /* FTSMPS has other bits in the pull down control register. */
+ reg = pdata->pull_down_enable
+ ? QPNP_COMMON_PULL_DOWN_ENABLE_MASK : 0;
+ rc = qpnp_vreg_masked_read_write(vreg,
+ QPNP_COMMON_REG_PULL_DOWN, reg,
+ QPNP_COMMON_PULL_DOWN_ENABLE_MASK);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Set soft start for LDO. */
+ if ((type == QPNP_REGULATOR_LOGICAL_TYPE_LDO
+ || type == QPNP_REGULATOR_LOGICAL_TYPE_ULT_LDO)
+ && pdata->soft_start_enable != QPNP_REGULATOR_USE_HW_DEFAULT) {
+ reg = pdata->soft_start_enable
+ ? QPNP_LDO_SOFT_START_ENABLE_MASK : 0;
+ rc = qpnp_vreg_write(vreg, QPNP_LDO_REG_SOFT_START, ®, 1);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Set soft start strength and over current protection for VS. */
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_VS) {
+ reg = 0;
+ mask = 0;
+ if (pdata->soft_start_enable != QPNP_REGULATOR_USE_HW_DEFAULT) {
+ reg |= pdata->soft_start_enable
+ ? QPNP_VS_SOFT_START_ENABLE_MASK : 0;
+ mask |= QPNP_VS_SOFT_START_ENABLE_MASK;
+ }
+ if (pdata->vs_soft_start_strength
+ != QPNP_VS_SOFT_START_STR_HW_DEFAULT) {
+ reg |= pdata->vs_soft_start_strength
+ & QPNP_VS_SOFT_START_SEL_MASK;
+ mask |= QPNP_VS_SOFT_START_SEL_MASK;
+ }
+ rc = qpnp_vreg_masked_read_write(vreg, QPNP_VS_REG_SOFT_START,
+ reg, mask);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (pdata->ocp_enable != QPNP_REGULATOR_USE_HW_DEFAULT) {
+ reg = pdata->ocp_enable ? QPNP_VS_OCP_NO_OVERRIDE
+ : QPNP_VS_OCP_OVERRIDE;
+ rc = qpnp_vreg_write(vreg, QPNP_VS_REG_OCP, ®, 1);
+ if (rc) {
+ vreg_err(vreg, "spmi write failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ /* Calculate the slew rate for FTSMPS regulators. */
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS) {
+ rc = qpnp_regulator_ftsmps_init_slew_rate(vreg);
+ if (rc) {
+ vreg_err(vreg, "failed to initialize step rate, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* Calculate the slew rate for FTSMPS2 regulators. */
+ if (type == QPNP_REGULATOR_LOGICAL_TYPE_FTSMPS2) {
+ rc = qpnp_regulator_ftsmps2_init_slew_rate(vreg);
+ if (rc) {
+ vreg_err(vreg, "failed to initialize step rate, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ vreg->init_mode = vreg->ctrl_reg[QPNP_COMMON_IDX_MODE];
+
+ return rc;
+}
+
+/* Fill in pdata elements based on values found in device tree. */
+static int qpnp_regulator_get_dt_config(struct platform_device *pdev,
+ struct qpnp_regulator_platform_data *pdata)
+{
+ unsigned int base;
+ struct device_node *node = pdev->dev.of_node;
+ int rc = 0;
+
+ pdata->init_data.constraints.input_uV
+ = pdata->init_data.constraints.max_uV;
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+ pdata->base_addr = base;
+
+ /* OCP IRQ is optional so ignore get errors. */
+ pdata->ocp_irq = platform_get_irq_byname(pdev, "ocp");
+ if (pdata->ocp_irq < 0)
+ pdata->ocp_irq = 0;
+
+ /*
+ * Initialize configuration parameters to use hardware default in case
+ * no value is specified via device tree.
+ */
+ pdata->auto_mode_enable = QPNP_REGULATOR_USE_HW_DEFAULT;
+ pdata->bypass_mode_enable = QPNP_REGULATOR_USE_HW_DEFAULT;
+ pdata->ocp_enable = QPNP_REGULATOR_USE_HW_DEFAULT;
+ pdata->pull_down_enable = QPNP_REGULATOR_USE_HW_DEFAULT;
+ pdata->soft_start_enable = QPNP_REGULATOR_USE_HW_DEFAULT;
+ pdata->boost_current_limit = QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT;
+ pdata->pin_ctrl_enable = QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT;
+ pdata->pin_ctrl_hpm = QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT;
+ pdata->vs_soft_start_strength = QPNP_VS_SOFT_START_STR_HW_DEFAULT;
+ pdata->hpm_enable = QPNP_REGULATOR_USE_HW_DEFAULT;
+
+ /* These bindings are optional, so it is okay if they are not found. */
+ of_property_read_u32(node, "qcom,auto-mode-enable",
+ &pdata->auto_mode_enable);
+ of_property_read_u32(node, "qcom,bypass-mode-enable",
+ &pdata->bypass_mode_enable);
+ of_property_read_u32(node, "qcom,ocp-enable", &pdata->ocp_enable);
+ of_property_read_u32(node, "qcom,ocp-max-retries",
+ &pdata->ocp_max_retries);
+ of_property_read_u32(node, "qcom,ocp-retry-delay",
+ &pdata->ocp_retry_delay_ms);
+ of_property_read_u32(node, "qcom,pull-down-enable",
+ &pdata->pull_down_enable);
+ of_property_read_u32(node, "qcom,soft-start-enable",
+ &pdata->soft_start_enable);
+ of_property_read_u32(node, "qcom,boost-current-limit",
+ &pdata->boost_current_limit);
+ of_property_read_u32(node, "qcom,pin-ctrl-enable",
+ &pdata->pin_ctrl_enable);
+ of_property_read_u32(node, "qcom,pin-ctrl-hpm", &pdata->pin_ctrl_hpm);
+ of_property_read_u32(node, "qcom,hpm-enable", &pdata->hpm_enable);
+ of_property_read_u32(node, "qcom,vs-soft-start-strength",
+ &pdata->vs_soft_start_strength);
+ of_property_read_u32(node, "qcom,system-load", &pdata->system_load);
+ of_property_read_u32(node, "qcom,enable-time", &pdata->enable_time);
+
+ return rc;
+}
+
+static const struct of_device_id spmi_match_table[];
+
+#define MAX_NAME_LEN 127
+
+static int qpnp_regulator_probe(struct platform_device *pdev)
+{
+ struct regulator_config reg_config = {};
+ struct qpnp_regulator_platform_data *pdata;
+ struct qpnp_regulator *vreg;
+ struct regulator_desc *rdesc;
+ struct qpnp_regulator_platform_data of_pdata;
+ struct regulator_init_data *init_data;
+ char *reg_name;
+ int rc;
+ bool is_dt;
+
+ vreg = kzalloc(sizeof(struct qpnp_regulator), GFP_KERNEL);
+ if (!vreg)
+ return -ENOMEM;
+
+ vreg->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!vreg->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ is_dt = of_match_device(spmi_match_table, &pdev->dev);
+
+ /* Check if device tree is in use. */
+ if (is_dt) {
+ init_data = of_get_regulator_init_data(&pdev->dev,
+ pdev->dev.of_node,
+ &vreg->rdesc);
+ if (!init_data) {
+ dev_err(&pdev->dev, "%s: unable to allocate memory\n",
+ __func__);
+ kfree(vreg);
+ return -ENOMEM;
+ }
+ memset(&of_pdata, 0,
+ sizeof(struct qpnp_regulator_platform_data));
+ memcpy(&of_pdata.init_data, init_data,
+ sizeof(struct regulator_init_data));
+
+ if (of_get_property(pdev->dev.of_node, "parent-supply", NULL))
+ of_pdata.init_data.supply_regulator = "parent";
+
+ rc = qpnp_regulator_get_dt_config(pdev, &of_pdata);
+ if (rc) {
+ dev_err(&pdev->dev, "%s: DT parsing failed, rc=%d\n",
+ __func__, rc);
+ kfree(vreg);
+ return -ENOMEM;
+ }
+
+ pdata = &of_pdata;
+ } else {
+ pdata = pdev->dev.platform_data;
+ }
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "%s: no platform data specified\n",
+ __func__);
+ kfree(vreg);
+ return -EINVAL;
+ }
+
+ vreg->pdev = pdev;
+ vreg->prev_write_count = -1;
+ vreg->write_count = 0;
+ vreg->base_addr = pdata->base_addr;
+ vreg->enable_time = pdata->enable_time;
+ vreg->system_load = pdata->system_load;
+ vreg->ocp_enable = pdata->ocp_enable;
+ vreg->ocp_irq = pdata->ocp_irq;
+ vreg->ocp_max_retries = pdata->ocp_max_retries;
+ vreg->ocp_retry_delay_ms = pdata->ocp_retry_delay_ms;
+
+ if (vreg->ocp_max_retries == 0)
+ vreg->ocp_max_retries = QPNP_VS_OCP_DEFAULT_MAX_RETRIES;
+ if (vreg->ocp_retry_delay_ms == 0)
+ vreg->ocp_retry_delay_ms = QPNP_VS_OCP_DEFAULT_RETRY_DELAY_MS;
+
+ rdesc = &vreg->rdesc;
+ rdesc->id = to_spmi_device(pdev->dev.parent)->ctrl->nr;
+ rdesc->owner = THIS_MODULE;
+ rdesc->type = REGULATOR_VOLTAGE;
+
+ reg_name = kzalloc(strnlen(pdata->init_data.constraints.name,
+ MAX_NAME_LEN) + 1, GFP_KERNEL);
+ if (!reg_name) {
+ kfree(vreg);
+ return -ENOMEM;
+ }
+ strlcpy(reg_name, pdata->init_data.constraints.name,
+ strnlen(pdata->init_data.constraints.name, MAX_NAME_LEN) + 1);
+ rdesc->name = reg_name;
+
+ dev_set_drvdata(&pdev->dev, vreg);
+
+ rc = qpnp_regulator_match(vreg);
+ if (rc)
+ goto bail;
+
+ if (is_dt && rdesc->ops) {
+ /* Fill in ops and mode masks when using device tree. */
+ if (rdesc->ops->enable)
+ pdata->init_data.constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_STATUS;
+ if (rdesc->ops->get_voltage)
+ pdata->init_data.constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_VOLTAGE;
+ if (rdesc->ops->get_mode) {
+ pdata->init_data.constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_MODE
+ | REGULATOR_CHANGE_DRMS;
+ pdata->init_data.constraints.valid_modes_mask
+ = REGULATOR_MODE_NORMAL | REGULATOR_MODE_IDLE;
+ }
+ }
+
+ rc = qpnp_regulator_init_registers(vreg, pdata);
+ if (rc) {
+ vreg_err(vreg, "common initialization failed, rc=%d\n", rc);
+ goto bail;
+ }
+
+ if (vreg->logical_type != QPNP_REGULATOR_LOGICAL_TYPE_VS)
+ vreg->ocp_irq = 0;
+
+ if (vreg->ocp_irq) {
+ rc = devm_request_irq(&pdev->dev, vreg->ocp_irq,
+ qpnp_regulator_vs_ocp_isr, IRQF_TRIGGER_RISING, "ocp",
+ vreg);
+ if (rc < 0) {
+ vreg_err(vreg, "failed to request irq %d, rc=%d\n",
+ vreg->ocp_irq, rc);
+ goto bail;
+ }
+
+ INIT_DELAYED_WORK(&vreg->ocp_work, qpnp_regulator_vs_ocp_work);
+ }
+
+ reg_config.dev = &pdev->dev;
+ reg_config.init_data = &pdata->init_data;
+ reg_config.driver_data = vreg;
+ reg_config.of_node = pdev->dev.of_node;
+ vreg->rdev = regulator_register(rdesc, ®_config);
+ if (IS_ERR(vreg->rdev)) {
+ rc = PTR_ERR(vreg->rdev);
+ if (rc != -EPROBE_DEFER)
+ vreg_err(vreg, "regulator_register failed, rc=%d\n",
+ rc);
+ goto cancel_ocp_work;
+ }
+
+ if (qpnp_vreg_debug_mask & QPNP_VREG_DEBUG_INIT && vreg->slew_rate)
+ pr_info("%-11s: step rate=%d uV/us\n", vreg->rdesc.name,
+ vreg->slew_rate);
+
+ qpnp_vreg_show_state(vreg->rdev, QPNP_REGULATOR_ACTION_INIT);
+
+ return 0;
+
+cancel_ocp_work:
+ if (vreg->ocp_irq)
+ cancel_delayed_work_sync(&vreg->ocp_work);
+bail:
+ if (rc && rc != -EPROBE_DEFER)
+ vreg_err(vreg, "probe failed, rc=%d\n", rc);
+
+ kfree(vreg->rdesc.name);
+ kfree(vreg);
+
+ return rc;
+}
+
+static int qpnp_regulator_remove(struct platform_device *pdev)
+{
+ struct qpnp_regulator *vreg;
+
+ vreg = dev_get_drvdata(&pdev->dev);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ if (vreg) {
+ regulator_unregister(vreg->rdev);
+ if (vreg->ocp_irq)
+ cancel_delayed_work_sync(&vreg->ocp_work);
+ kfree(vreg->rdesc.name);
+ kfree(vreg);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = QPNP_REGULATOR_DRIVER_NAME, },
+ {}
+};
+
+static const struct platform_device_id qpnp_regulator_id[] = {
+ { QPNP_REGULATOR_DRIVER_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(spmi, qpnp_regulator_id);
+
+static struct platform_driver qpnp_regulator_driver = {
+ .driver = {
+ .name = QPNP_REGULATOR_DRIVER_NAME,
+ .of_match_table = spmi_match_table,
+ .owner = THIS_MODULE,
+ },
+ .probe = qpnp_regulator_probe,
+ .remove = qpnp_regulator_remove,
+ .id_table = qpnp_regulator_id,
+};
+
+/*
+ * Pre-compute the number of set points available for each regulator type to
+ * avoid unnecessary calculations later in runtime.
+ */
+static void qpnp_regulator_set_point_init(void)
+{
+ struct qpnp_voltage_set_points **set_points;
+ int i, j, temp;
+
+ set_points = all_set_points;
+
+ for (i = 0; i < ARRAY_SIZE(all_set_points); i++) {
+ temp = 0;
+ for (j = 0; j < all_set_points[i]->count; j++) {
+ all_set_points[i]->range[j].n_voltages
+ = (all_set_points[i]->range[j].set_point_max_uV
+ - all_set_points[i]->range[j].set_point_min_uV)
+ / all_set_points[i]->range[j].step_uV + 1;
+ if (all_set_points[i]->range[j].set_point_max_uV == 0)
+ all_set_points[i]->range[j].n_voltages = 0;
+ temp += all_set_points[i]->range[j].n_voltages;
+ }
+ all_set_points[i]->n_voltages = temp;
+ }
+}
+
+/**
+ * qpnp_regulator_init() - register spmi driver for qpnp-regulator
+ *
+ * This initialization function should be called in systems in which driver
+ * registration ordering must be controlled precisely.
+ */
+int __init qpnp_regulator_init(void)
+{
+ static bool has_registered;
+
+ if (has_registered)
+ return 0;
+ has_registered = true;
+
+ qpnp_regulator_set_point_init();
+
+ return platform_driver_register(&qpnp_regulator_driver);
+}
+EXPORT_SYMBOL(qpnp_regulator_init);
+
+static void __exit qpnp_regulator_exit(void)
+{
+ platform_driver_unregister(&qpnp_regulator_driver);
+}
+
+MODULE_DESCRIPTION("QPNP PMIC regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(qpnp_regulator_init);
+module_exit(qpnp_regulator_exit);
diff --git a/include/linux/power/qcom/apm.h b/include/linux/power/qcom/apm.h
new file mode 100644
index 0000000..8ac8863
--- /dev/null
+++ b/include/linux/power/qcom/apm.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_POWER_QCOM_APM_H__
+#define __LINUX_POWER_QCOM_APM_H__
+
+#include <linux/device.h>
+#include <linux/err.h>
+
+/**
+ * enum msm_apm_supply - supported power rails to supply memory arrays
+ * %MSM_APM_SUPPLY_APCC: to enable selection of VDD_APCC rail as supply
+ * %MSM_APM_SUPPLY_MX: to enable selection of VDD_MX rail as supply
+ */
+enum msm_apm_supply {
+ MSM_APM_SUPPLY_APCC,
+ MSM_APM_SUPPLY_MX,
+};
+
+/* Handle used to identify an APM controller device */
+struct msm_apm_ctrl_dev;
+
+#ifdef CONFIG_MSM_APM
+struct msm_apm_ctrl_dev *msm_apm_ctrl_dev_get(struct device *dev);
+int msm_apm_set_supply(struct msm_apm_ctrl_dev *ctrl_dev,
+ enum msm_apm_supply supply);
+int msm_apm_get_supply(struct msm_apm_ctrl_dev *ctrl_dev);
+
+#else
+static inline struct msm_apm_ctrl_dev *msm_apm_ctrl_dev_get(struct device *dev)
+{ return ERR_PTR(-EPERM); }
+static inline int msm_apm_set_supply(struct msm_apm_ctrl_dev *ctrl_dev,
+ enum msm_apm_supply supply)
+{ return -EPERM; }
+static inline int msm_apm_get_supply(struct msm_apm_ctrl_dev *ctrl_dev)
+{ return -EPERM; }
+#endif
+#endif
diff --git a/include/linux/regulator/msm-ldo-regulator.h b/include/linux/regulator/msm-ldo-regulator.h
new file mode 100644
index 0000000..ad04e29
--- /dev/null
+++ b/include/linux/regulator/msm-ldo-regulator.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#ifndef __MSM_LDO_REGULATOR_H__
+#define __MSM_LDO_REGULATOR_H__
+
+/**
+ * enum msm_ldo_supply_mode - supported operating modes by this regulator type.
+ * Use negative logic to ensure BHS mode is treated as the safe default by the
+ * the regulator framework. This is necessary since LDO mode can only be enabled
+ * when several constraints are satisfied. Consumers of this regulator are
+ * expected to request changes in operating modes through the use of
+ * regulator_allow_bypass() passing in the desired LDO supply mode.
+ * %BHS_MODE: to select BHS as operating mode
+ * %LDO_MODE: to select LDO as operating mode
+ */
+enum msm_ldo_supply_mode {
+ BHS_MODE = false,
+ LDO_MODE = true,
+};
+
+#endif /* __MSM_LDO_REGULATOR_H__ */
diff --git a/include/linux/regulator/qpnp-regulator.h b/include/linux/regulator/qpnp-regulator.h
new file mode 100644
index 0000000..36288c0
--- /dev/null
+++ b/include/linux/regulator/qpnp-regulator.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2012-2013, 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_QPNP_REGULATOR_H__
+#define __REGULATOR_QPNP_REGULATOR_H__
+
+#include <linux/regulator/machine.h>
+
+#define QPNP_REGULATOR_DRIVER_NAME "qcom,qpnp-regulator"
+
+/* Pin control enable input pins. */
+#define QPNP_REGULATOR_PIN_CTRL_ENABLE_NONE 0x00
+#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN0 0x01
+#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN1 0x02
+#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN2 0x04
+#define QPNP_REGULATOR_PIN_CTRL_ENABLE_EN3 0x08
+#define QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT 0x10
+
+/* Pin control high power mode input pins. */
+#define QPNP_REGULATOR_PIN_CTRL_HPM_NONE 0x00
+#define QPNP_REGULATOR_PIN_CTRL_HPM_EN0 0x01
+#define QPNP_REGULATOR_PIN_CTRL_HPM_EN1 0x02
+#define QPNP_REGULATOR_PIN_CTRL_HPM_EN2 0x04
+#define QPNP_REGULATOR_PIN_CTRL_HPM_EN3 0x08
+#define QPNP_REGULATOR_PIN_CTRL_HPM_SLEEP_B 0x10
+#define QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT 0x20
+
+/*
+ * Used with enable parameters to specify that hardware default register values
+ * should be left unaltered.
+ */
+#define QPNP_REGULATOR_DISABLE 0
+#define QPNP_REGULATOR_ENABLE 1
+#define QPNP_REGULATOR_USE_HW_DEFAULT 2
+
+/* Soft start strength of a voltage switch type regulator */
+enum qpnp_vs_soft_start_str {
+ QPNP_VS_SOFT_START_STR_0P05_UA,
+ QPNP_VS_SOFT_START_STR_0P25_UA,
+ QPNP_VS_SOFT_START_STR_0P55_UA,
+ QPNP_VS_SOFT_START_STR_0P75_UA,
+ QPNP_VS_SOFT_START_STR_HW_DEFAULT,
+};
+
+/* Current limit of a boost type regulator */
+enum qpnp_boost_current_limit {
+ QPNP_BOOST_CURRENT_LIMIT_300_MA,
+ QPNP_BOOST_CURRENT_LIMIT_600_MA,
+ QPNP_BOOST_CURRENT_LIMIT_900_MA,
+ QPNP_BOOST_CURRENT_LIMIT_1200_MA,
+ QPNP_BOOST_CURRENT_LIMIT_1500_MA,
+ QPNP_BOOST_CURRENT_LIMIT_1800_MA,
+ QPNP_BOOST_CURRENT_LIMIT_2100_MA,
+ QPNP_BOOST_CURRENT_LIMIT_2400_MA,
+ QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT,
+};
+
+/**
+ * struct qpnp_regulator_platform_data - qpnp-regulator initialization data
+ * @init_data: regulator constraints
+ * @pull_down_enable: 1 = Enable output pull down resistor when the
+ * regulator is disabled
+ * 0 = Disable pull down resistor
+ * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify
+ * pull down state
+ * @pin_ctrl_enable: Bit mask specifying which hardware pins should be
+ * used to enable the regulator, if any
+ * Value should be an ORing of
+ * QPNP_REGULATOR_PIN_CTRL_ENABLE_* constants. If
+ * the bit specified by
+ * QPNP_REGULATOR_PIN_CTRL_ENABLE_HW_DEFAULT is
+ * set, then pin control enable hardware registers
+ * will not be modified.
+ * @pin_ctrl_hpm: Bit mask specifying which hardware pins should be
+ * used to force the regulator into high power
+ * mode, if any
+ * Value should be an ORing of
+ * QPNP_REGULATOR_PIN_CTRL_HPM_* constants. If
+ * the bit specified by
+ * QPNP_REGULATOR_PIN_CTRL_HPM_HW_DEFAULT is
+ * set, then pin control mode hardware registers
+ * will not be modified.
+ * @system_load: Load in uA present on regulator that is not captured
+ * by any consumer request
+ * @enable_time: Time in us to delay after enabling the regulator
+ * @ocp_enable: 1 = Allow over current protection (OCP) to be
+ * enabled for voltage switch type regulators so
+ * that they latch off automatically when over
+ * current is detected. OCP is enabled when in HPM
+ * or auto mode.
+ * 0 = Disable OCP
+ * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify
+ * OCP state
+ * @ocp_irq: IRQ number of the voltage switch OCP IRQ. If
+ * specified the voltage switch will be toggled off
+ * and back on when OCP triggers in order to handle
+ * high in-rush current.
+ * @ocp_max_retries: Maximum number of times to try toggling a voltage
+ * switch off and back on as a result of
+ * consecutive over current events.
+ * @ocp_retry_delay_ms: Time to delay in milliseconds between each
+ * voltage switch toggle after an over current
+ * event takes place.
+ * @boost_current_limit: This parameter sets the current limit of boost type
+ * regulators. Its value should be one of
+ * QPNP_BOOST_CURRENT_LIMIT_*. If its value is
+ * QPNP_BOOST_CURRENT_LIMIT_HW_DEFAULT, then the
+ * boost current limit will be left at its default
+ * hardware value.
+ * @soft_start_enable: 1 = Enable soft start for LDO and voltage switch
+ * type regulators so that output voltage slowly
+ * ramps up when the regulator is enabled
+ * 0 = Disable soft start
+ * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify
+ * soft start state
+ * @vs_soft_start_strength: This parameter sets the soft start strength for
+ * voltage switch type regulators. Its value
+ * should be one of QPNP_VS_SOFT_START_STR_*. If
+ * its value is QPNP_VS_SOFT_START_STR_HW_DEFAULT,
+ * then the soft start strength will be left at its
+ * default hardware value.
+ * @auto_mode_enable: 1 = Enable automatic hardware selection of regulator
+ * mode (HPM vs LPM). Auto mode is not available
+ * on boost type regulators
+ * 0 = Disable auto mode selection
+ * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify
+ * auto mode state
+ * @bypass_mode_enable: 1 = Enable bypass mode for an LDO type regulator so
+ * that it acts like a switch and simply outputs
+ * its input voltage
+ * 0 = Do not enable bypass mode
+ * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify
+ * bypass mode state
+ * @hpm_enable: 1 = Enable high power mode (HPM), also referred to
+ * as NPM. HPM consumes more ground current than
+ * LPM, but it can source significantly higher load
+ * current. HPM is not available on boost type
+ * regulators. For voltage switch type regulators,
+ * HPM implies that over current protection and
+ * soft start are active all the time. This
+ * configuration can be overwritten by changing the
+ * regulator's mode dynamically.
+ * 0 = Do not enable HPM
+ * QPNP_REGULATOR_USE_HW_DEFAULT = do not modify
+ * HPM state
+ * @base_addr: SMPI base address for the regulator peripheral
+ */
+struct qpnp_regulator_platform_data {
+ struct regulator_init_data init_data;
+ int pull_down_enable;
+ unsigned int pin_ctrl_enable;
+ unsigned int pin_ctrl_hpm;
+ int system_load;
+ int enable_time;
+ int ocp_enable;
+ int ocp_irq;
+ int ocp_max_retries;
+ int ocp_retry_delay_ms;
+ enum qpnp_boost_current_limit boost_current_limit;
+ int soft_start_enable;
+ enum qpnp_vs_soft_start_str vs_soft_start_strength;
+ int auto_mode_enable;
+ int bypass_mode_enable;
+ int hpm_enable;
+ u16 base_addr;
+};
+
+#ifdef CONFIG_REGULATOR_QPNP
+
+/**
+ * qpnp_regulator_init() - register spmi driver for qpnp-regulator
+ *
+ * This initialization function should be called in systems in which driver
+ * registration ordering must be controlled precisely.
+ */
+int __init qpnp_regulator_init(void);
+
+#else
+
+static inline int __init qpnp_regulator_init(void)
+{
+ return -ENODEV;
+}
+
+#endif /* CONFIG_REGULATOR_QPNP */
+
+#endif