Merge "regulator: add snapshot of cpr-regulator driver and documentation"
diff --git a/Documentation/devicetree/bindings/regulator/cpr-regulator.txt b/Documentation/devicetree/bindings/regulator/cpr-regulator.txt
new file mode 100644
index 0000000..1c4dfbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/cpr-regulator.txt
@@ -0,0 +1,978 @@
+QTI CPR (Core Power Reduction) Regulator
+
+CPR regulator device is for QTI RBCPR (RapidBridge CPR) on
+	application processor core. It takes voltage corner level
+	as input and converts it to actual voltage based on the
+	suggestions from factory production process. When CPR is
+	enabled for application processer core, it will suggest
+	scaling the voltage up or down for best performance and
+	power of the core. The scaling based on factory production
+	process is called PVS (Process Voltage Scaling) with efuse
+	bits to indicate what bin (and voltage range) a chip is in.
+
+Required properties:
+- compatible:			Must be "qcom,cpr-regulator"
+- reg:				Register addresses for RBCPR, RBCPR clock
+				select, PVS and CPR eFuse address
+- reg-names:			Register names. Must be "rbcpr" and "efuse_addr".
+				"rbcpr_clk" is optional.
+- regulator-name:		A string used to describe the regulator
+- interrupts:			Interrupt line from RBCPR to interrupt controller.
+- qcom,cpr-fuse-corners:	Number of fuse corners present.  Many other properties
+				are sized based upon this value.
+- regulator-min-microvolt:	Minimum corner value which should be 1 to
+				represent the lowest supported corner.
+- regulator-max-microvolt:	Maximum corner value which should be equal to
+				qcom,cpr-fuse-corners if consumers request fuse
+				corners or the length of qcom,cpr-corner-map if
+				consumers request virtual corners.
+- qcom,cpr-voltage-ceiling:	Array of ceiling voltages in microvolts for fuse
+				corners ordered from lowest voltage corner to highest
+				voltage corner.  This property must be of length
+				defined by qcom,cpr-fuse-corners.
+- qcom,cpr-voltage-floor:	Array of floor voltages in microvolts for fuse
+				corners ordered from lowest voltage corner to highest
+				voltage corner.  This property must be of length
+				defined by qcom,cpr-fuse-corners.
+- vdd-apc-supply:		Regulator to supply VDD APC power
+- qcom,vdd-apc-step-up-limit:	Limit of vdd-apc-supply steps for scaling up.
+- qcom,vdd-apc-step-down-limit:	Limit of vdd-apc-supply steps for scaling down.
+- qcom,cpr-ref-clk:		The reference clock in kHz.
+- qcom,cpr-timer-delay:		The delay in microseconds for the timer interval.
+- qcom,cpr-timer-cons-up:	Consecutive number of timer interval (qcom,cpr-timer-delay)
+				occurred before issuing UP interrupt.
+- qcom,cpr-timer-cons-down:	Consecutive number of timer interval (qcom,cpr-timer-delay)
+				occurred before issuing DOWN interrupt.
+- qcom,cpr-irq-line:		Internal interrupt route signal of RBCPR, one of 0, 1 or 2.
+- qcom,cpr-step-quotient:	Defines the number of CPR quotient (i.e. Ring Oscillator(RO)
+				count) per vdd-apc-supply output voltage step.  A single
+				integer value may be specified which is to be used for all
+				RO's.  Alternatively, 8 integer values may be specified which
+				define the step quotients for RO0 to RO7 in order.
+- qcom,cpr-up-threshold:	The threshold for CPR to issue interrupt when
+				error_steps is greater than it when stepping up.
+- qcom,cpr-down-threshold:	The threshold for CPR to issue interrupt when
+				error_steps is greater than it when stepping down.
+- qcom,cpr-idle-clocks:		Idle clock cycles RO can be in.
+- qcom,cpr-gcnt-time:		The time for gate count in microseconds.
+- qcom,cpr-apc-volt-step:	The voltage in microvolt per CPR step, such as 5000uV.
+- qcom,cpr-fuse-row:		Array of row number of CPR fuse and method to read that row. It should have
+				index and value like this:
+				 [0] => the fuse row number
+				 [1] => fuse reading method, 0 for direct reading or 1 for SCM reading
+- qcom,cpr-fuse-target-quot:	Array of bit positions in the primary CPR fuse row defined
+				by qcom,cpr-fuse-row for the target quotients of each
+				fuse corner.  Each bit position corresponds to the LSB
+				of the quotient parameter.  The elements in the array
+				are ordered from lowest voltage corner to highest voltage
+				corner.  This property must be of length defined by
+				qcom,cpr-fuse-corners.
+- qcom,cpr-fuse-ro-sel:		Array of bit positions in the primary CPR fuse row defined
+				by qcom,cpr-fuse-row for the ring oscillator selection for each
+				fuse corner.  Each bit position corresponds to the LSB
+				of the RO select parameter.  The elements in the array
+				are ordered from lowest voltage corner to highest voltage
+				corner.  This property must be of length defined by
+				qcom,cpr-fuse-corners.
+
+Optional properties:
+- vdd-mx-supply:		Regulator to supply memory power as dependency
+				of VDD APC.
+- qcom,vdd-mx-vmax:		The maximum voltage in uV for vdd-mx-supply. This
+				is required when vdd-mx-supply is present.
+- qcom,vdd-mx-vmin-method:	The method to determine the minimum voltage for
+				vdd-mx-supply, which can be one of following
+				choices compared with VDD APC:
+				  0 => equal to the voltage(vmin) of VDD APC
+				  1 => equal to PVS corner ceiling voltage
+				  2 => equal to slow speed corner ceiling
+				  3 => equal to qcom,vdd-mx-vmax
+				  4 => equal to VDD_APC fuse corner mapped vdd-mx voltage
+				  5 => equal to VDD_APC virtual corner mapped vdd-mx voltage
+				This is required when vdd-mx-supply is present.
+- qcom,vdd-mx-corner-map:	Array of integers which defines the mapping from VDD_APC
+				voltage corners to vdd-mx-supply voltages.
+				Each element is a voltage to request from vdd-mx for the
+				corresponding fuse corner or virtual corner. The elements
+				in the array are ordered from lowest voltage corner
+				to highest voltage corner.  The length of this property
+				depends on the value of qcom,vdd-mx-vmin-method property.
+				When qcom,vdd-mx-vmin-method property has a value of 4, the length
+				of this property must be equal to the value defined by qcom,cpr-fuse-corners.
+				When qcom,vdd-mx-vmin-method property has a value of 5, the length of
+				this property must be equal to the number of elements in the qcom,cpr-corner-map
+				property.
+- qcom,pvs-voltage-table: 	Array of N-tuples in which each tuple specifies the
+				initial voltage in microvolts of the PVS bin for each
+				fuse voltage corner.  The location or 0-based index
+				of a tuple in the list corresponds to the PVS bin number.
+				Each tuple must be of length defined by qcom,cpr-fuse-corners.
+				A given cpr-regulator device must have either
+				qcom,pvs-voltage-table specified or
+				qcom,cpr-fuse-init-voltage (and its associated properties).
+- qcom,pvs-fuse-redun-sel:	Array of 5 elements to indicate where to read the bits, what value to
+				compare with in order to decide if the redundant PVS fuse bits would be
+				used instead of the original bits and method to read fuse row, reading
+				register through SCM or directly. The 5 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] => the value to indicate redundant selection
+				  [4] => fuse reading method, 0 for direct reading or 1 for SCM reading
+				When the value of the fuse bits specified by first 3 elements equals to
+				the value in 4th element, redundant PVS fuse bits should be selected.
+				Otherwise, the original PVS bits should be selected. If the 5th
+				element is 0, read the fuse row from register directly. Otherwise,
+				read it through SCM.
+				This property is required if qcom,pvs-voltage-table is present.
+- qcom,pvs-fuse:		Array of 4 elements to indicate the bits for PVS fuse and read method.
+				The array should have index and value like this:
+				  [0] => the PVS fuse row number
+				  [1] => LSB bit position of the bits
+				  [2] => number of bits
+				  [3] => fuse reading method, 0 for direct reading or 1 for SCM reading
+				This property is required if qcom,pvs-voltage-table is present.
+- qcom,pvs-fuse-redun:		Array of 4 elements to indicate the bits for redundant PVS fuse.
+				The array should have index and value like this:
+				  [0] => the redundant PVS fuse row number
+				  [1] => LSB bit position of the bits
+				  [2] => number of bits
+				  [3] => fuse reading method, 0 for direct reading or 1 for SCM reading
+				This property is required if qcom,pvs-voltage-table is present.
+- qcom,cpr-fuse-redun-sel:	Array of 5 elements to indicate where to read the bits, what value to
+				compare with in order to decide if the redundant CPR fuse bits would be
+				used instead of the original bits and method to read fuse row, using SCM
+				to read or read register directly. The 5 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] => the value to indicate redundant selection
+				  [4] => fuse reading method, 0 for direct reading or 1 for SCM reading
+				When the value of the fuse bits specified by first 3 elements equals to
+				the value in 4th element, redundant CPR fuse bits should be selected.
+				Otherwise, the original CPR bits should be selected. If the 5th element
+				is 0, read the fuse row from register directly. Otherwise, read it through
+				SCM.
+- qcom,cpr-fuse-redun-row:	Array of row number of redundant CPR fuse and method to read that
+				row. It should have index and value like this:
+				 [0] => the redundant fuse row number
+				 [1] => the value to indicate reading the fuse row directly or using SCM
+				This property is required if qcom,cpr-fuse-redun-sel is present.
+- qcom,cpr-fuse-redun-target-quot:	Array of bit positions in the redundant CPR fuse row defined
+				by qcom,cpr-fuse-redun-row for the target quotients of each
+				fuse corner.  Each bit position corresponds to the LSB
+				of the quotient parameter.  The elements in the array
+				are ordered from lowest voltage corner to highest voltage corner.
+				This property must be of length defined by qcom,cpr-fuse-corners.
+				This property is required if qcom,cpr-fuse-redun-sel is present.
+- qcom,cpr-fuse-redun-ro-sel:	Array of bit positions in the redundant CPR fuse row defined
+				by qcom,cpr-fuse-redun-row for the ring oscillator select of each
+				fuse corner.  Each bit position corresponds to the LSB of the RO
+				select parameter.  The elements in the array are ordered from
+				lowest voltage corner to highest voltage corner.
+				This property must be of length defined by qcom,cpr-fuse-corners.
+				This property is required if qcom,cpr-fuse-redun-sel is present.
+- qcom,cpr-fuse-redun-bp-cpr-disable:	Redundant bit position of the bit to indicate if CPR should be disable
+- qcom,cpr-fuse-redun-bp-scheme:	Redundant bit position of the bit to indicate if it's a global/local scheme
+					This property is required if cpr-fuse-redun-bp-cpr-disable
+					is present, and vise versa.
+- qcom,cpr-fuse-bp-cpr-disable:	Bit position of the bit to indicate if CPR should be disabled
+- qcom,cpr-fuse-bp-scheme:     Bit position of the bit to indicate if it's a global/local scheme
+- qcom,cpr-fuse-revision:	Array of 4 integer elements which define the location of the bits for
+				the CPR fusing revision fuse parameter.  The 4 elements are:
+				[0]: => the fuse row number of the bits
+				[1]: => LSB bit position of the bits
+				[2]: => the number of bits
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading
+				The fusing revision value is used to determine which specific adjustments
+				are required on some chips.
+- qcom,cpr-fuse-target-quot-size:	Array of target quotient parameter bit sizes in the primary
+				or redundant CPR fuse row for each fuse corner.  The elements in the
+				array are ordered from lowest voltage corner to highest voltage corner.
+				If this property is not present, then all target quotient fuse values
+				are assumed to be the default length of 12 bits.
+- qcom,cpr-fuse-target-quot-scale:	Array of doubles which defines the scaling coefficients to decode
+				the target quotients of each fuse corner.  The first element in each
+				double represents the offset to add to the scaled quotient.  The second
+				element represents the multiplier to scale the quotient by.  For example,
+				given a tuple <A B>, quot_decoded = A + (B * quot_raw).
+				The doubles in the array are ordered from lowest voltage corner to highest
+				voltage corner.  This property must contain a number of doubles equal to
+				the value of qcom,cpr-fuse-corners.  If this property is not present,
+				then all target quotient parameters are assumed to have an offset of 0
+				and a multiplier of 1 (i.e. no decoding needed).
+- qcom,cpr-enable:		Present: CPR enabled by default.
+				Not Present: CPR disable by default.
+- qcom,cpr-fuse-cond-min-volt-sel:	Array of 5 elements to indicate where to read the bits,  what value to
+				compare with in order to decide if the conditional minimum apc voltage needs
+				to be applied and the fuse reading method.
+				The 5 elements with index[0..4] are:
+				[0] => the fuse row number;
+				[1] => LSB bit position of the bits;
+				[2] => number of the bits;
+				[3] => the expected data to read;
+				[4] => fuse reading method, 0 for direct reading or 1 for SCM reading;
+				When the value of the fuse bits specified by first 3 elements is not equal to
+				the value in 4th element, then set the apc voltage for all parts running
+				at each voltage corner to be not lower than the voltage defined
+				using "qcom,cpr-cond-min-voltage".
+- qcom,cpr-cond-min-voltage:	Minimum voltage in microvolts allowed for cpr-regulator output if the fuse bits
+				defined in qcom,cpr-fuse-cond-min-volt-sel have not been programmed with the
+				expected data. This is required if cpr-fuse-cond-min-volt-sel is present.
+- qcom,cpr-fuse-uplift-sel: 	Array of 5 elements to indicate where to read the bits, what value to
+				compare with in order to enable or disable the pvs voltage uplift workaround,
+				and the fuse reading method.
+				The 5 elements with index[0..4] are:
+				[0]: => the fuse row number of the selector;
+				[1]: => LSB bit position of the bits;
+				[2]: => number of the bits;
+				[3]: => the value to indicate if the apc pvs voltage uplift workaround will
+					be enabled;
+				[4]: => fuse reading method, 0 for direct reading or 1 for SCM reading.
+				When the value of the fuse bits specified by first 3 elements equals to the
+				value in 4th element, the pvs voltage uplift workaround will be enabled.
+- qcom,speed-bin-fuse-sel:	Array of 4 elements to indicate where to read the speed bin of the processor,
+				and the fuse reading method.
+				The 4 elements with index[0..3] are:
+				[0]: => the fuse row number of the selector;
+				[1]: => LSB bit position of the bits;
+				[2]: => number of the bits;
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading.
+				This is required if cpr-fuse-uplift-disable-sel is present.
+- qcom,cpr-uplift-voltage:	Uplift in microvolts used for increasing pvs init voltage. If this property is present,
+				This is required if cpr-fuse-uplift-disable-sel is present.
+- qcom,cpr-uplift-max-volt:	Maximum voltage in microvolts used for pvs voltage uplift workaround to limit
+				the maximum pvs voltage.
+				This is required if cpr-fuse-uplift-disable-sel is present.
+- qcom,cpr-uplift-quotient:	Array of target quotient increments to add to the fused quotients of each
+				fuse corner as part of the PVS voltage uplift workaround.
+				The elements in the array are ordered from lowest voltage
+				corner to highest voltage corner.  This property must be of
+				length defined by qcom,cpr-fuse-corners.  This is required
+				if cpr-fuse-uplift-disable-sel is present.
+- qcom,cpr-uplift-speed-bin:	The speed bin value corresponding to one type of processor which needs to apply the
+				pvs voltage uplift workaround.
+				This is required if cpr-fuse-uplift-disable-sel is present.
+- qcom,cpr-fuse-version-map:	Array of integer tuples which each match to a given combination of CPR
+				fuse parameter values.  Each tuple consists of N + 3 elements.  Where
+				N is the number of fuse corners defined by the qcom,cpr-fuse-corners
+				property.  The elements in one tuple are:
+				[0]: =>		the speed bin of the CPU
+				[1]: =>		the PVS version of the CPU
+				[2]: =>		the CPR fuse revision
+				[3 - N+2]: =>	the ring oscillator select value of each fuse corner
+						ordered from lowest to highest
+				Any element in a tuple may use the value 0xffffffff as a wildcard
+				which will match against any fuse parameter value.  The first tuple
+				that matches against the fuse values read from hardware will be used.
+				This property is used by several properties to provide an index into
+				their lists.
+- qcom,cpr-allowed:		Integer values that specifies whether the closed loop CPR is allowed or
+				not for a particular fuse revision. If the qcom,cpr-fuse-version-map
+				property is specified, then qcom,cpr-allowed must contain the same number
+				of integers as that of the number of tuples in qcom,cpr-fuse-version-map.
+				If the integer value has a value 0 for a particular fuse revision, then it
+				is treated as if the closed loop operation is disabled in the fuse. If the
+				integer value has a value 1 for a particular fuse revision, then the closed
+				loop operation is enabled for that fuse revision. If nothing is specified
+				for a particular fuse revision, then the closed loop operation is enabled
+				for that fuse revision by default.
+- qcom,cpr-quotient-adjustment:	Array of integer tuples of target quotient adjustments to add to the fused
+				quotients of each fuse corner.  The elements in a tuple are ordered from
+				lowest voltage corner to highest voltage corner.  Each tuple must be of
+				length defined by qcom,cpr-fuse-corners.  If the qcom,cpr-fuse-version-map
+				property is specified, then qcom,cpr-quotient-adjustment must contain the
+				same number of tuples as qcom,cpr-fuse-version-map.  These tuples are then
+				mapped one-to-one in the order specified.  E.g. if the second
+				qcom,cpr-fuse-version-map tuple matches for a given device, then the quotient
+				adjustments defined in the second qcom,cpr-quotient-adjustment tuple will
+				be applied.  If the qcom,cpr-fuse-version-map property is not specified,
+				then qcom,cpr-quotient-adjustment must contain a single tuple which is then
+				applied unconditionally.  If this property is specified, then the quotient
+				adjustment values are added to the target quotient values read from fuses
+				before writing them into the CPR GCNT target control registers.
+				This property can be used to add or subtract static voltage margin from the
+				regulator managed by the CPR controller.
+- qcom,cpr-init-voltage-adjustment:  Array of integer tuples of initial voltage adjustments in microvolts to
+				add to the fused initial voltage values of each fuse corner.  The elements
+				in a tuple are ordered from lowest voltage corner to highest voltage corner.
+				Each tuple must be of the length defined by qcom,cpr-fuse-corners.  If the
+				qcom,cpr-fuse-version-map property is specified, then
+				qcom,cpr-init-voltage-adjustment must contain the same number of tuples as
+				qcom,cpr-fuse-version-map.  These tuples are then mapped one-to-one in the
+				order specified.  E.g. if the second qcom,cpr-fuse-version-map tuple matches
+				for a given device, then the initial voltage adjustments defined in the
+				second qcom,cpr-init-voltage-adjustment tuple will be applied.  If the
+				qcom,cpr-fuse-version-map property is not specified, then
+				qcom,cpr-init-voltage-adjustment must contain a single tuple which is then
+				applied unconditionally.  This property can be used to add or subtract
+				static initial voltage margin from the regulator managed by the CPR
+				controller.
+- qcom,cpr-quot-offset-adjustment:	Array of integer tuples of target quotient offset adjustments to add
+				to the fused quotient offsets of each fuse corner. The elements in a tuple
+				are ordered from lowest voltage corner to highest voltage corner. Each tuple
+				must be of length defined by qcom,cpr-fuse-corners. If the qcom,cpr-fuse-version-map
+				property is specified, then qcom,cpr-quot-offset-adjustment must contain the
+				same number of tuples as qcom,cpr-fuse-version-map.  These tuples are then
+				mapped one-to-one in the order specified.  E.g. if the second
+				qcom,cpr-fuse-version-map tuple matches for a given device, then the quotient
+				offset adjustments defined in the second qcom,cpr-quot-offset-adjustment tuple
+				will be applied. If the qcom,cpr-fuse-version-map property is not specified,
+				then qcom,cpr-quot-offset-adjustment must contain a single tuple which is then
+				applied unconditionally.  If this property is specified, then the quotient
+				offset adjustment values are added to the target quotient offset values read
+				from fuses.
+				This property can be used to add or subtract static quotient offset margin from
+				the regulator managed by the CPR controller.
+- qcom,cpr-clamp-timer-interval:	The number of 64 reference clock cycle blocks to delay for whenever
+					the clamp signal, sensor mask registers or sensor bypass registers
+					change.  The CPR controller loop is disabled during this delay.
+					Supported values are 0 to 255.  If this property is not specified,
+					then a value of 0 is assumed.  Note that if this property has a
+					value greater than 0, then software cannot accurately determine the
+					error_steps value that corresponds to a given CPR measurement
+					unless processor power collapsing is disabled.  If this property
+					has a value of 0, then the CPR controller loop is not disabled and
+					re-enabled while idle if the clamp signal changes.  Instead, it
+					will remain idle until software issues an ACK or NACK command.
+					This ensures that software can read the error_steps value which
+					resulted in the CPR up or down interrupt.  Setting this property to
+					a value greater than 0 is useful for resetting the CPR sensors of a
+					processor that uses BHS type voltage switches in order to avoid
+					anomalous CPR up interrupts when exiting from power collapse.
+- vdd-apc-optional-prim-supply:	Present: Regulator of highest priority to supply VDD APC power
+				Not Present: No such regulator.
+- vdd-apc-optional-sec-supply:	Present: Regulator of second highest priority to supply VDD APC power.
+				Not Present: No such regulator.
+- qcom,cpr-speed-bin-max-corners: Array of (N+2)-tuples in which each tuple maps a CPU speed bin and PVS version to
+				the maximum virtual voltage corner corresponding to each fuse corner.  The value N
+				corresponds to the number of fuse corners specified by qcom,cpr-fuse-corners.
+				The elements in one tuple are:
+				[0]: =>		the speed bin of the CPU. It may use the value 0xffffffff as a
+						wildcard to match any speed bin values.
+				[1]: =>		the PVS version of the CPU. It may use the value 0xffffffff as
+						a wildcard to match any PVS version values.
+				[2 - N+1]: =>	the max virtual voltage corner value corresponding to each fuse corner
+						for this speed bin, ordered from lowest voltage corner to highest
+						voltage corner.
+				No CPR target quotient scaling is applied on chips which have a speed bin + PVS version
+				pair that does not appear in one of the tuples in this property. If the property is
+				specified, then quotient scaling is enabled for the highest voltage corner. If this property is
+				not specified, then no quotient scaling can take place.
+- qcom,cpr-corner-map:		Array of elements of fuse corner value for each virtual corner.
+				The location or 1-based index of an element in the list corresponds to
+				the virtual corner value. For example, the first element in the list is the fuse corner
+				value that virtual corner 1 maps to.
+				This property is required if qcom,cpr-speed-bin-max-corners is present.
+- qcom,cpr-corner-frequency-map: Array of tuples in which a tuple describes a corner to application processor frequency
+				mapping.
+				The 2 elements in one tuple are:
+				[0]: => a virtual voltage corner.
+				[1]: => the application processor frequency in Hz corresponding to the virtual corner.
+				This property is required if qcom,cpr-speed-bin-max-corners is present.
+- qcom,pvs-version-fuse-sel:	Array of 4 elements to indicate where to read the pvs version of the processor,
+				and the fuse reading method.
+				The 4 elements with index[0..3] are:
+				[0]: => the fuse row number of the selector;
+				[1]: => LSB bit position of the bits;
+				[2]: => the number of bits;
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading.
+- qcom,cpr-voltage-ceiling-override: Array of (N+2)-tuples in which each tuple maps a CPU speed bin and PVS version
+				to the ceiling voltage to apply for each virtual voltage corner.  The value N
+				corresponds to the number of virtual corners as specified by the number of elements
+				in the qcom,cpr-corner-map property.
+				The elements in one tuple are:
+				[0]: =>		the speed bin of the CPU. It may use the value 0xffffffff as a
+						wildcard to match any speed bin values.
+				[1]: =>		the PVS version of the CPU. It may use the value 0xffffffff as a
+						wildcard to match any PVS version values.
+				[2 - N+1]: =>	the ceiling voltage value in microvolts corresponding to each virtual
+						corner for this speed bin, ordered from lowest voltage corner to
+						highest voltage corner.
+				No ceiling override is applied on chips which have a speed bin + PVS version
+				pair that does not appear in one of the tuples in this property.  If the property is
+				specified and the speed bin + PVS version matches, then the per-virtual-corner ceiling
+				voltages will be used in place of the per-fuse-corner ceiling voltages defined in the
+				qcom,cpr-voltage-ceiling property.  If this property is not specified, then the
+				per-fuse-corner ceiling voltages will always be used.
+- qcom,cpr-voltage-floor-override: Array of (N+2)-tuples in which each tuple maps a CPU speed bin and PVS version
+				to the floor voltage to apply for each virtual voltage corner.  The value N
+				corresponds to the number of virtual corners as specified by the number of elements
+				in the qcom,cpr-corner-map property.
+				The elements in one tuple are:
+				[0]: =>		the speed bin of the CPU. It may use the value 0xffffffff as a
+						wildcard to match any speed bin values.
+				[1]: =>		the PVS version of the CPU. It may use the value 0xffffffff as a
+						wildcard to match any PVS version values.
+				[2 - N+1]: =>	the floor voltage value in microvolts corresponding to each virtual
+						corner for this speed bin, ordered from lowest voltage corner to
+						highest voltage corner.
+				No floor override is applied on chips which have a speed bin + PVS version
+				pair that does not appear in one of the tuples in this property.  If the property is
+				specified and the speed bin + PVS version matches, then the per-virtual-corner floor
+				voltages will be used in place of the per-fuse-corner floor voltages defined in the
+				qcom,cpr-voltage-floor property.  If this property is not specified, then the
+				per-fuse-corner floor voltages will always be used.
+- qcom,cpr-floor-to-ceiling-max-range:	Array of integer tuples of floor-to-ceiling max range values in microvolts
+				to be subtracted from the ceiling voltage values of each virtual corner.
+				Supported values are those greater than or equal 0, or (-1). The value 0 for a corner
+				implies that the floor value for that corner has to equal to its ceiling value.
+				The value (-1) for a corner implies that no modification to the default floor voltage
+				is required. The elements in a tuple are ordered from lowest voltage corner to highest
+				voltage corner. Each tuple must be of the length equal to the number of virtual corners
+				as specified by the number of elements in the qcom,cpr-corner-map property. If the
+				qcom,cpr-fuse-version-map property is specified, then
+				qcom,cpr-dynamic-floor-override-adjustment must contain the same number of
+				tuples as qcom,cpr-fuse-version-map.  These tuples are then mapped one-to-one in the
+				order specified.  E.g. if the second qcom,cpr-fuse-version-map tuple matches
+				for a given device, then voltage adjustments defined in the second
+				qcom,cpr-dynamic-floor-override-adjustment tuple will be applied.  If the
+				qcom,cpr-fuse-version-map property is not specified, then
+				qcom,cpr-dynamic-floor-override-adjustment must contain a single tuple which
+				is then applied unconditionally.
+- qcom,cpr-virtual-corner-init-voltage-adjustment: Array of integer tuples of voltage adjustments in microvolts to be
+				added to the initial voltage values of each virtual corner.  The elements
+				in a tuple are ordered from lowest voltage corner to highest voltage corner.
+				Each tuple must be of the length equal to the number of virtual corners as
+				specified by the number of elements in the qcom,cpr-corner-map property. If the
+				qcom,cpr-fuse-version-map property is specified, then
+				qcom,cpr-virtual-corner-init-voltage-adjustment must contain the same number of
+				tuples as qcom,cpr-fuse-version-map.  These tuples are then mapped one-to-one in the
+				order specified.  E.g. if the second qcom,cpr-fuse-version-map tuple matches
+				for a given device, then voltage adjustments defined in the second
+				qcom,cpr-virtual-corner-init-voltage-adjustment tuple will be applied.  If the
+				qcom,cpr-fuse-version-map property is not specified, then
+				qcom,cpr-virtual-corner-init-voltage-adjustment must contain a single tuple which
+				is then applied unconditionally.
+- qcom,cpr-virtual-corner-quotient-adjustment: Array of integer tuples of quotient offsets to be added to
+				the scaled target quotient of each virtual corner. The elements
+				in a tuple are ordered from lowest voltage corner to highest voltage corner.
+				Each tuple must be of the length equal to the number of virtual corners as
+				specified by the number of elements in the qcom,cpr-corner-map property.
+				If the qcom,cpr-fuse-version-map property is specified, then
+				qcom,cpr-virtual-corner-quotient-adjustment must contain the same number of tuples as
+				qcom,cpr-fuse-version-map.  These tuples are then mapped one-to-one in the
+				order specified.  E.g. if the second qcom,cpr-fuse-version-map tuple matches
+				for a given device, then quotient adjustments defined in the second
+				qcom,cpr-virtual-corner-quotient-adjustment tuple will be applied.  If the
+				qcom,cpr-fuse-version-map property is not specified, then
+				qcom,cpr-virtual-corner-quotient-adjustment must contain a single tuple which is then
+				applied unconditionally.
+- qcom,cpr-cpus:		Array of CPU phandles which correspond to the cores that this cpr-regulator
+				device must monitor when adjusting the voltage and/or target quotient based
+				upon the number of online cores or make sure that one of them must be online
+				when performing de-aging measurements. This property must be specified in order to
+				utilize the qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment or
+				qcom,cpr-online-cpu-virtual-corner-quotient-adjustment or qcom,cpr-aging-sensor-id properties.
+- qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment:	Array of tuples where each tuple specifies
+				the voltage adjustment for each corner. These adjustments apply to the
+				initial voltage of each corner. The size of each tuple must be equal
+				to qcom,cpr-fuse-corners if consumers request fuse corners or the length of
+				qcom,cpr-corner-map if consumers request virtual corners. In each tuple, the
+				value corresponds to the voltage adjustment when running at that corner at
+				init, from lowest to highest. The tuples must be organized into 1 group if
+				qcom,cpr-fuse-version-map is not specified or the same number of groups as
+				the number of tuples in qcom,cpr-fuse-version-map. The i-th group of tuples
+				corresponds to the voltage adjustments for i-th fuse version map tuple. In
+				each group, there are 1 plus length of qcom,cpr-cpus tuples, each tuple
+				corresponds to the number of cores online, from 0 to the number of elements
+				in qcom,cpr-cpus.
+- qcom,cpr-online-cpu-init-voltage-as-ceiling:	Boolean which indicates that the ceiling voltage used for a
+				given virtual corner may be reduced to the per number of cores online,
+				per-virtual corner ceiling voltage value. This property takes precedence
+				over qcom,cpr-scaled-init-voltage-as-ceiling if both are specified.
+- qcom,cpr-online-cpu-virtual-corner-quotient-adjustment:	Array of tuples where each tuple specifies
+				the quotient adjustment for each corner. These adjustments will be applied
+				to each corner at run time. The size of each tuple must be equal to
+				qcom,cpr-fuse-corners if consumers request fuse corners or the length of
+				qcom,cpr-corner-map if consumers request virtual corners. In each tuple,
+				the value corresponds to the quotient adjustment when running at that corner,
+				from lowest to highest. The tuples must be organized into 1 group if
+				qcom,cpr-fuse-version-map is not specified or the same number of groups
+				as the number of tuples in qcom,cpr-fuse-version-map. The i-th group of
+				tuples corresponds to the quotient adjustments for i-th fuse version map
+				tuple. In each group, there are 1 plus length of qcom,cpr-cpus tuples,
+				each tuple corresponds to the number of cores online, from 0 to the
+				number of elements in qcom,cpr-cpus.
+- qcom,cpr-init-voltage-as-ceiling: Boolean which indicates that the ceiling voltage used for a given virtual
+				corner may be reduced to the per-fuse-corner initial voltage fuse value.
+- qcom,cpr-scaled-init-voltage-as-ceiling: Boolean which indicates that the ceiling voltage used for a given
+				virtual corner may be reduced to the interpolated, per-virtual-corner initial
+				voltage value.  Note that if both qcom,cpr-init-voltage-as-ceiling and
+				qcom,cpr-scaled-init-voltage-as-ceiling are specified, then
+				qcom,cpr-scaled-init-voltage-as-ceiling will take precedence since the interpolated
+				voltages are necessarily less than or equal to the fused initial voltage values.
+- qcom,cpr-voltage-scaling-factor-max: Array of values which define the maximum allowed scaling factor to apply
+				when calculating per-corner initial voltage values for each fuse corner.  The
+				array must be of length equal to the value of the qcom,cpr-fuse-corners property.
+				Each element in the array maps to the fuse corners in increasing order.
+				The elements have units of uV/MHz.  Each element corresponds to 'max_factor' in
+				the following equation:
+				init_voltage_min(f) = fuse_init_voltage(f) - (fuse_f_max - f) * max_factor
+				If this property is not specified, then the initial voltage for each virtual
+				corner will be set to the initial voltage of the associated fuse corner.
+- qcom,cpr-quot-adjust-scaling-factor-max: Array of values which define the maximum allowed scaling factor to
+				apply when calculating per-virtual-corner target quotients for each fuse
+				corner.  Two data formats are allowed for this property.  The primary one
+				requires that the array be of length equal to the value of the
+				qcom,cpr-fuse-corners property.  When using this format, each element in the
+				array maps to the fuse corners in increasing order.  The second depreciated
+				format allows for only a single element to be specified which defines the
+				maximum scaling factor for the highest fuse corner.  In this case, a value of
+				0 is assumed for the lower fuse corners.  The elements of this property have
+				units of QUOT/GHz.  Each element corresponds to 'max_factor' in the following
+				equation:
+				quot_min(f) = fuse_quot(f) - (fuse_f_max - f) * max_factor / 1000
+				where f and fuse_f_max have units of MHz.
+				This property is required if qcom,cpr-speed-bin-max-corners is present.
+- qcom,cpr-fuse-init-voltage:	Array of quadruples in which each quadruple specifies a fuse location to
+				read in order to get an initial voltage for a fuse corner. The fuse values
+				are encoded as voltage steps higher or lower than the voltages defined in
+				qcom,cpr-voltage-ceiling. Each step corresponds to the voltage defined by
+				the qcom,cpr-init-voltage-step property.
+				The 4 elements in one quadruple are:
+				[0]: => the fuse row number of the bits
+				[1]: => LSB bit position of the bits
+				[2]: => number of the bits
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading
+				The quadruples are ordered from the lowest voltage fuse corner to the
+				highest voltage fuse corner.
+				A given cpr-regulator device must have either qcom,cpr-fuse-init-voltage
+				specified or qcom,pvs-voltage-table (and its associated properties).
+- qcom,cpr-fuse-redun-init-voltage: Array of quadruples in which each quadruple specifies a fuse location
+				to read in order to get the redundant initial voltage for a fuse corner.
+				This property is the same as qcom,cpr-fuse-init-voltage except that it is
+				only utilized if a chip is configured to use the redundant set of fuse
+				values.  This property is required if qcom,cpr-fuse-redun-sel and
+				qcom,cpr-fuse-init-voltage are specified.
+- qcom,cpr-init-voltage-ref:	Array of reference voltages in microvolts used when decoding the initial
+				voltage fuse values.  The elements in the array are ordered from lowest
+				voltage corner to highest voltage corner.  This property must be of length
+				defined by qcom,cpr-fuse-corners.
+				This property is required if qcom,cpr-fuse-init-voltage is present.
+- qcom,cpr-init-voltage-step:	The voltage step size in microvolts of the CPR initial voltage fuses described by the
+				qcom,cpr-fuse-init-voltage property.
+				This property is required if qcom,cpr-fuse-init-voltage is present.
+- mem-acc-supply:		Regulator to vote for the memory accelerator configuration.
+				Not Present: memory accelerator configuration not supported.
+- qcom,mem-acc-corner-map:	Array of integer which defines the mapping from mem-acc corner value for each
+				virtual corner. Each element is a mem-acc state for the corresponding virtual corner.
+				The elements in the array are ordered from lowest voltage corner to highest voltage corner.
+- qcom,fuse-remap-source:	Array of quadruples in which each quadruple specifies a fuse location to
+				remap.  The 4 elements in one quadruple are:
+				[0]: => the fuse row number of the bits
+				[1]: => LSB bit position of the bits
+				[2]: => the number of bits
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading
+				The fuse bits for all quadruples are packed together in the order specified
+				into 64-bit virtual fuse rows beginning at the row number defined in the
+				qcom,fuse-remap-base-row property.  The remapped rows may be used by any
+				other properties.
+				Example:
+					qcom,fuse-remap-base-row = <1000>;
+					qcom,fuse-remap-source =
+							<13 57 2 0>,
+							<14 30 3 0>,
+							<20 1 7 0>,
+							<40 47 120 0>;
+
+					This results in the following bit remapping:
+
+					Row   Bits       Remap Row  Remap Bits
+					13    57..58  -->  1000      0..1
+					14    30..32  -->  1000      2..4
+					20     1..7   -->  1000      5..11
+					40    47..63  -->  1000     12..28
+					41     0..34  -->  1000     29..63
+					41    35..63  -->  1001      0..28
+					42     0..34  -->  1001     29..63
+					42    35..38  -->  1002      0..3
+
+					A tuple like this could then be used to reference some of the
+					concatenated bits from rows 13, 14, and 20:
+
+					qcom,cpr-fuse-init-voltage = <1000 0 6 0>;
+- qcom,fuse-remap-base-row:	Integer which defines the virtual row number to use as a base when remapping
+				fuse bits.  The remap base row number can be any value as long as it is
+				greater than all of the real row numbers addressed in other properties of
+				the cpr-regulator device node.  This property is required if
+				qcom,fuse-remap-source is specified.
+- qcom,cpr-quot-min-diff:	Integer which defines the minimum target-quotient difference between
+				the highest and (highest - 1) fuse corner to keep CPR enabled. If this
+				property is not specified a default value of 50 is used.
+- qcom,cpr-fuse-quot-offset:	Array of quadruples in which each quadruple specifies a fuse location to
+				read in order to get the quotient offset for a fuse corner. The fuse values
+				are encoded as the difference between quotients of that fuse corner and its
+				adjacent lower fuse corner divided by an unpacking multiplier value defined
+				under qcom,cpr-fuse-quot-offset-scale property.
+				The 4 elements in one quadruple are:
+				[0]: => the fuse row number of the bits
+				[1]: => LSB bit position of the bits
+				[2]: => number of the bits
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading
+				The quadruples are ordered from the lowest fuse corner to the highest
+				fuse corner.
+				Quotient offset read from the fuse locations above can be overridden with
+				the property qcom,cpr-quot-adjust-scaling-factor-max.
+- qcom,cpr-fuse-quot-offset-scale:	Array of integer values which defines the multipliers to decode the quotient offsets
+				of each fuse corner. The elements in the array are ordered from the lowest voltage fuse corner
+				to the highest voltage fuse corner. If this property is not present, then all target quotient
+				parameters are assumed to have a multiplier of 1 (i.e. no decoding needed).
+- qcom,cpr-redun-fuse-quot-offset: Array of quadruples in which each quadruple specifies a fuse location to
+				read in order to get the redundant quotient offset for a fuse corner. This
+				property is the same as qcom,cpr-fuse-quot-offset except that it is only
+				utilized if a chip is configured to use the redundant set of fuse values.
+- qcom,cpr-fuse-min-quot-diff:	Array of values which define the minimum difference allowed between the adjusted
+				quotients of the fuse corners. The length of the array should be equal to the value
+				of the qcom,cpr-fuse-corners property. Where each element in the array maps to the
+				fuse corners in increasing order.
+- qcom,cpr-min-quot-diff-adjustment:	Array of integer tuples of target quotient offsets to be added to
+				the adjusted target quotients of each fuse corner. When the quotient difference
+				between two adjacent fuse corners is insufficient, the quotient for the higher fuse corner is
+				replaced with that of the lower fuse corner plus the adjustment value.
+				The elements in a tuple are ordered from lowest voltage corner to highest voltage corner.
+				Each tuple must be of the length defined by qcom,cpr-fuse-corners.
+				If the qcom,cpr-fuse-version-map property is specified, then qcom,cpr-min-quot-diff-adjustment
+				must contain the same number of tuples as qcom,cpr-fuse-version-map.  These tuples are then mapped
+				one-to-one in the order specified.  E.g. if the second qcom,cpr-fuse-version-map tuple matches
+				for a given device, then the quotient adjustments defined in the
+				second qcom,cpr-min-quot-diff-adjustment tuple will be applied.  If the
+				qcom,cpr-fuse-version-map property is not specified, then
+				qcom,cpr-min-quot-diff-adjustment must contain a single tuple which is then
+				applied unconditionally. The qcom,cpr-min-quot-diff-adjustment property must be specified
+				if the qcom,cpr-fuse-min-quot-diff property is specified.
+- qcom,cpr-skip-voltage-change-during-suspend: Boolean property which indicates that the CPR voltage
+				should not be adjusted based upon the number of online cores while
+				entering or exiting system suspend.
+- rpm-apc-supply:		Regulator to notify RPM of the APC operating
+				corner
+- qcom,rpm-apc-corner-map:	Array of integers which define the mapping of
+				the RPM corner to the corresponding APC virtual
+				corner. This property must be defined if
+				'rpm-apc-supply' is present.
+- qcom,vsens-corner-map:	Array of integers which define the mapping of the VSENS corner to the
+				corresponding APC fuse corner. The qcom,vsens-corner-map and
+				vdd-vsense-corner-supply properties must both be specified for a given
+				cpr-regulator device or neither must be specified.
+- vdd-vsens-corner-supply:	Regulator to specify the current operating fuse corner to the Voltage Sensor.
+- vdd-vsens-voltage-supply:	Regulator to specify the corner floor/ceiling voltages to the Voltage Sensor.
+- qcom,cpr-aging-sensor-id:	Array of CPR sensor IDs to be used in the CPR de-aging algorithm. The number
+				of values should be equal to number of sensors selected for age calibration.
+				If this property is not specified, then the de-aging procedure is not enabled.
+- qcom,cpr-de-aging-allowed:	Integer values that specify whether the CPR de-aging procedure is allowed or
+				not for a particular fuse revision. If the qcom,cpr-fuse-version-map
+				property is specified, then qcom,cpr-de-aging-allowed must contain the same number
+				of elements as there are tuples in qcom,cpr-fuse-version-map. If qcom,cpr-fuse-version-map
+				is not specified, then qcom,cpr-de-aging-allowed must contain a single value that
+				is used unconditionally. An element value of 1 means that the CPR de-aging procedure
+				can be performed for parts with the corresponding fuse revision. An element value of 0
+				means that CPR de-aging cannot be performed.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-aging-ref-corner:	The vdd-apc-supply reference virtual voltage corner to be set during the CPR de-aging
+				measurements. This corner value is needed to set appropriate voltage on
+				the dependent voltage rails such as vdd-mx and mem-acc.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-aging-ref-voltage:	The vdd-apc-supply reference voltage in microvolts to be set during the
+				CPR de-aging measurements.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-max-aging-margin:	The maximum allowed aging voltage margin in microvolts. This is used to limit
+				the calculated aging voltage margin.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-non-collapsible-sensors: Array of CPR sensor IDs which are in non-collapsible domain. The sensor IDs not
+				specified in the array should be bypassed for the de-aging procedure. The number of
+				elements should be less than or equal to 32. The values of the array elements should
+				be greater than or equal to 0 and less than or equal to 31.
+				This property is required for power-domains with bypass mux present in HW.
+				This property can be required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-aging-ro-scaling-factor:	The aging ring oscillator (RO) scaling factor with units of QUOT/V.
+				This value is used for calculating a voltage margin from RO measurements.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-ro-scaling-factor:	Array of scaling factors with units of QUOT/V for each ring oscillator ordered
+				from the lowest to the highest RO. These values are used to calculate
+				the aging voltage margin adjustment for all of the ROs. Since CPR2 supports
+				exactly 8 ROs, the array must contain 8 elements corresponding to RO0 through RO7 in order.
+				If a given RO is unused for a fuse corner, then its scaling factor may be specified as 0.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-aging-derate:	Array of scaling factors which define the amount of derating to apply to the reference
+				aging voltage margin adjustment for each of the fuse corners. Each element has units
+				of uV/mV. This property must be of length defined by qcom,cpr-fuse-corners.
+				The elements are ordered from the lowest to the highest fuse corner.
+				This property is required if the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-fuse-aging-init-quot-diff:	Array of quadruples in which each quadruple specifies a fuse location to read in
+				order to get an initial quotient difference. The difference between quot min and quot max
+				is fused as the initial quotient difference.
+				The 4 elements in one quadruple are:
+				[0]: => the fuse row number of the bits
+				[1]: => LSB bit position of the bits
+				[2]: => number of the bits
+				[3]: => fuse reading method, 0 for direct reading or 1 for SCM reading
+				The number of quadruples should be equal to the number of values specified in
+				the qcom,cpr-aging-sensor-id property. This property is required if
+				the qcom,cpr-aging-sensor-id property has been specified.
+- qcom,cpr-thermal-sensor-id:	TSENS hardware sensor-id of the sensor which
+				needs to be monitored.
+- qcom,cpr-disable-temp-threshold:	The TSENS temperature threshold in degrees Celsius at which CPR
+				closed-loop is disabled. CPR closed-loop will stay disabled as long as the
+				temperature is below this threshold. This property is required
+				only if 'qcom,cpr-thermal-sensor-id' is present.
+- qcom,cpr-enable-temp-threshold:	The TSENS temperature threshold in degrees Celsius at which CPR
+				closed-loop is enabled. CPR closed-loop will stay enabled above this
+				temperature threshold. This property is required only if
+				'qcom,cpr-thermal-sensor-id' is present.
+- qcom,disable-closed-loop-in-pc:	Bool property to disable closed-loop CPR during
+				power-collapse. This can be enabled only for single core
+				designs. The property 'qcom,cpr-cpus' is required to enable this logic.
+Example:
+	apc_vreg_corner: regulator@f9018000 {
+		status = "okay";
+		compatible = "qcom,cpr-regulator";
+		reg = <0xf9018000 0x1000>, <0xfc4b8000 0x1000>;
+		reg-names = "rbcpr", "efuse_addr";
+		interrupts = <0 15 0>;
+		regulator-name = "apc_corner";
+		qcom,cpr-fuse-corners = <3>;
+		regulator-min-microvolt = <1>;
+		regulator-max-microvolt = <12>;
+
+		qcom,pvs-fuse = <22 6 5 1>;
+		qcom,pvs-fuse-redun-sel = <22 24 3 2 1>;
+		qcom,pvs-fuse-redun = <22 27 5 1>;
+
+		qcom,pvs-voltage-table =
+			<1050000 1150000 1350000>,
+			<1050000 1150000 1340000>,
+			<1050000 1150000 1330000>,
+			<1050000 1150000 1320000>,
+			<1050000 1150000 1310000>,
+			<1050000 1150000 1300000>,
+			<1050000 1150000 1290000>,
+			<1050000 1150000 1280000>,
+			<1050000 1150000 1270000>,
+			<1050000 1140000 1260000>,
+			<1050000 1130000 1250000>,
+			<1050000 1120000 1240000>,
+			<1050000 1110000 1230000>,
+			<1050000 1100000 1220000>,
+			<1050000 1090000 1210000>,
+			<1050000 1080000 1200000>,
+			<1050000 1070000 1190000>,
+			<1050000 1060000 1180000>,
+			<1050000 1050000 1170000>,
+			<1050000 1050000 1160000>,
+			<1050000 1050000 1150000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>,
+			<1050000 1050000 1140000>;
+		qcom,cpr-voltage-ceiling = <1050000 1150000 1280000>;
+		qcom,cpr-voltage-floor = <1050000 1050000 1100000>;
+		vdd-apc-supply = <&pm8226_s2>;
+		vdd-apc-optional-prim-supply = <&ncp6335d>;
+		vdd-apc-optional-sec-supply = <&fan53555>;
+		vdd-mx-supply = <&pm8226_l3_ao>;
+		qcom,vdd-mx-vmax = <1350000>;
+		qcom,vdd-mx-vmin-method = <1>;
+		qcom,vdd-apc-step-up-limit = <1>;
+		qcom,vdd-apc-step-down-limit = <1>;
+		qcom,cpr-ref-clk = <19200>;
+		qcom,cpr-timer-delay = <5000>;
+		qcom,cpr-timer-cons-up = <1>;
+		qcom,cpr-timer-cons-down = <2>;
+		qcom,cpr-irq-line = <0>;
+		qcom,cpr-step-quotient = <15>;
+		qcom,cpr-up-threshold = <1>;
+		qcom,cpr-down-threshold = <2>;
+		qcom,cpr-idle-clocks = <5>;
+		qcom,cpr-gcnt-time = <1>;
+		qcom,cpr-clamp-timer-interval = <1>;
+		qcom,cpr-apc-volt-step = <5000>;
+
+		qcom,vsens-corner-map = <1 2 2>;
+		vdd-vsens-corner-supply = <&vsens_apc0_corner>;
+		vdd-vsens-voltage-supply = <&vsens_apc0_voltage>;
+
+		rpm-apc-supply = <&rpm_apc_vreg>;
+		qcom,rpm-apc-corner-map = <4 4 5 5 7 7 7 7 7 7 7 7>;
+
+		qcom,cpr-fuse-row = <138 1>;
+		qcom,cpr-fuse-bp-cpr-disable = <36>;
+		qcom,cpr-fuse-bp-scheme = <37>;
+		qcom,cpr-fuse-target-quot = <24 12 0>;
+		qcom,cpr-fuse-target-quot-size = <12 12 12>;
+		qcom,cpr-fuse-ro-sel = <54 38 41>;
+		qcom,cpr-fuse-revision = <140 26 2 0>;
+		qcom,cpr-fuse-redun-sel = <138 57 1 1 1>;
+		qcom,cpr-fuse-redun-row = <139 1>;
+		qcom,cpr-fuse-redun-target-quot = <24 12 0>;
+		qcom,cpr-fuse-redun-ro-sel = <46 36 39>;
+		qcom,cpr-fuse-cond-min-volt-sel = <54 42 6 7 1>;
+		qcom,cpr-cond-min-voltage = <1140000>;
+		qcom,cpr-fuse-uplift-sel = <22 53 1 0 0>;
+		qcom,cpr-uplift-voltage = <50000>;
+		qcom,cpr-uplift-quotient = <0 0 120>;
+		qcom,cpr-uplift-max-volt = <1350000>;
+		qcom,cpr-uplift-speed-bin = <1>;
+		qcom,speed-bin-fuse-sel = <22 0 3 0>;
+		qcom,cpr-corner-map = <1 1 2 2 3 3 3 3 3 3 3 3>;
+		qcom,cpr-corner-frequency-map =
+				<1 300000000>,
+				<2 384000000>,
+				<3 600000000>,
+				<4 787200000>,
+				<5 998400000>,
+				<6 1094400000>,
+				<7 1190400000>,
+				<8 1305600000>,
+				<9 1344000000>,
+				<10 1401600000>,
+				<11 1497600000>,
+				<12 1593600000>;
+		qcom,pvs-version-fuse-sel = <22 4 2 0>;
+		qcom,cpr-speed-bin-max-corners =
+				<0 1 2 4 7>,
+				<1 1 2 4 12>,
+				<2 1 2 4 10>,
+				<5 1 2 4 14>;
+		qcom,cpr-fuse-target-quot-scale =
+				<0 1>,
+				<0 1>,
+				<0 1>;
+		qcom,cpr-quot-adjust-scaling-factor-max = <0 650 650>;
+		qcom,cpr-fuse-quot-offset =
+				<138 53 5 0>,
+				<138 53 5 0>,
+				<138 48 5 0>,
+				<138 58 5 0>;
+		qcom,cpr-fuse-redun-quot-offset =
+				<200 53 5 0>,
+				<200 53 5 0>,
+				<200 48 5 0>,
+				<200 58 5 0>;
+		qcom,cpr-fuse-init-voltage =
+				<27 36 6 0>,
+				<27 18 6 0>,
+				<27 0 6 0>;
+		qcom,cpr-fuse-redun-init-voltage =
+				<140 36 6 0>,
+				<140 18 6 0>,
+				<140 0 6 0>;
+		qcom,cpr-init-voltage-ref = <1050000 1150000 1280000>;
+		qcom,cpr-init-voltage-step = <10000>;
+		qcom,cpr-voltage-ceiling-override =
+				<1 1 1050000 1050000 1150000 1150000 1280000
+				     1280000 1280000 1280000 1280000 1280000
+				     1280000 1280000>;
+		qcom,cpr-voltage-floor-override =
+				<1 1 1050000 1050000 1050000 1050000 1060000
+				     1070000 1080000 1090000 1100000 1100000
+				     1100000 1100000>;
+		qcom,cpr-scaled-init-voltage-as-ceiling;
+
+		qcom,cpr-fuse-version-map =
+				<0xffffffff 0xffffffff 2 4 4 4>,
+				<0xffffffff 0xffffffff 2 6 6 6>,
+				<0xffffffff 0xffffffff 3 4 4 4>;
+		qcom,cpr-quotient-adjustment =
+				<0 0 (-210)>,
+				<0 0 (-60)>,
+				<0 0 (-94)>;
+		qcom,cpr-quot-offset-adjustment =
+				<0 0 (-5)>;
+		qcom,cpr-init-voltage-adjustment =
+				<0 0 (-100000)>,
+				<0 0 (-100000)>,
+				<0 0 (-45000)>;
+		qcom,cpr-fuse-min-quot-diff = <0 0 40>;
+		qcom,cpr-min-quot-diff-adjustment =
+					<0 0 0>,
+					<0 0 72>,
+					<0 0 104>;
+		qcom,cpr-floor-to-ceiling-max-range =
+			<(-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1)>,
+			<(-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1) (-1)>,
+			<(-1) (-1) (-1) (-1) (-1) (-1) (-1) 50000 50000 50000 50000 50000>;
+		qcom,cpr-virtual-corner-init-voltage-adjustment =
+			<0 0 0 (-10000) 0 0 0 0 0 0 0 0>,
+			<0 0 0 0 0 0 0 0 0 0 0 (-20000)>,
+			<0 0 0 0 0 0 0 0 0 0 0 (-30000)>;
+		qcom,cpr-virtual-corner-quotient-adjustment =
+			<0 0 0 100 0 0 0 0 0 0 0 0>,
+			<0 0 0 0 0 0 0 0 0 0 0 (-300)>,
+			<0 0 0 (-60) 0 0 0 0 0 0 0 0>;
+		qcom,cpr-cpus = <&CPU0 &CPU1 &CPU2 &CPU3>;
+		qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment =
+			/* 1st fuse version tuple matched */
+			<0 0 0 (-10000) (-10000) (-10000) (-15000) (-15000) (-20000) 0 (-20000) (-30000) >, /* 0 CPUs online */
+			<0 0 0 (-10000) (-10000) (-10000) (-15000) (-15000) (-20000) 0 (-20000) (-30000) >, /* 1 CPUs online */
+			<0 0 0 (-5000) (-5000) (-5000) (-5000) (-5000) (-10000) 0 (-10000) (-10000) >, /* 2 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 3 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 4 CPUs online */
+			/* 2nd fuse version tuple matched */
+			<0 0 0 (-10000) (-10000) (-10000) (-15000) (-15000) (-20000) 0 (-20000) (-30000) >, /* 0 CPUs online */
+			<0 0 0 (-10000) (-10000) (-10000) (-15000) (-15000) (-20000) 0 (-20000) (-30000) >, /* 1 CPUs online */
+			<0 0 0 (-5000) (-5000) (-5000) (-5000) (-5000) (-10000) 0 (-10000) (-10000) >, /* 2 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 3 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 4 CPUs online */
+			/* 3rd fuse version tuple matched */
+			<0 0 0 (-10000) (-10000) (-10000) (-15000) (-15000) (-20000) 0 (-20000) (-30000) >, /* 0 CPUs online */
+			<0 0 0 (-10000) (-10000) (-10000) (-15000) (-15000) (-20000) 0 (-20000) (-30000) >, /* 1 CPUs online */
+			<0 0 0 (-5000) (-5000) (-5000) (-5000) (-5000) (-10000) 0 (-10000) (-10000) >, /* 2 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 3 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>; /* 4 CPUs online */
+		qcom,cpr-online-cpu-virtual-corner-quotient-adjustment =
+			/* 1st fuse version tuple matched */
+			<0 0 0 (-6) (-6) (-6) (-9) (-9) (-12) 0 (-12) (-18)>, /* 0 CPUs online */
+			<0 0 0 (-6) (-6) (-6) (-9) (-9) (-12) 0 (-12) (-18)>, /* 1 CPUs online */
+			<0 0 0 (-3) (-3) (-3) (-3) (-3) (-6) 0 (-6) (-6)>, /* 2 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 3 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 4 CPUs online */
+			/* 2nd fuse version tuple matched */
+			<0 0 0 (-6) (-6) (-6) (-9) (-9) (-12) 0 (-12) (-18)>, /* 0 CPUs online */
+			<0 0 0 (-6) (-6) (-6) (-9) (-9) (-12) 0 (-12) (-18)>, /* 1 CPUs online */
+			<0 0 0 (-3) (-3) (-3) (-3) (-3) (-6) 0 (-6) (-6)>, /* 2 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 3 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 4 CPUs online */
+			/* 3rd fuse version tuple matched */
+			<0 0 0 (-21) (-21) (-21) (-32) (-32) (-42) 0 (-42) (-63)>, /* 0 CPUs online */
+			<0 0 0 (-21) (-21) (-21) (-32) (-32) (-42) 0 (-42) (-63)>, /* 1 CPUs online */
+			<0 0 0 (-11) (-11) (-11) (-11) (-11) (-21) 0 (-21) (-21)>, /* 2 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>, /* 3 CPUs online */
+			<0 0 0 0 0 0 0 0 0 0 0 0>; /* 4 CPUs online */
+		qcom,cpr-allowed =
+			<0>,
+			<1>,
+			<1>;
+
+		qcom,fuse-remap-base-row = <1000>;
+		qcom,fuse-remap-source =
+				<140 7 3 0>,
+				<138 45 5 0>;
+		qcom,cpr-fuse-quot-offset-scale = <5 5 5>;
+
+		qcom,cpr-aging-sensor-id = <17, 18>;
+		qcom,cpr-aging-ref-corner = <4>;
+		qcom,cpr-aging-ref-voltage = <1050000>;
+		qcom,cpr-max-aging-margin = <15000>;
+		qcom,cpr-de-aging-allowed =
+				<0>,
+				<0>,
+				<1>;
+		qcom,cpr-non-collapsible-sensors= <7 12 17 22>;
+		qcom,cpr-aging-ro-scaling-factor = <3500>;
+		qcom,cpr-ro-scaling-factor = <0 2500 2500 2500 0 0 0 0>;
+		qcom,cpr-aging-derate = <1000 1000 1250>;
+		qcom,cpr-fuse-aging-init-quot-diff =
+				<101 0 8 0>,
+				<101 8 8 0>;
+
+		qcom,cpr-thermal-sensor-id = <9>;
+		qcom,cpr-disable-temp-threshold = <5>;
+		qcom,cpr-enable-temp-threshold = <10>;
+	};
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index ee1b322..1bd67e6 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -888,6 +888,16 @@
 	  This driver provides support for the voltage regulators on the
 	  WM8994 CODEC.
 
+config REGULATOR_CPR
+	bool "RBCPR regulator driver for APC"
+	depends on OF
+	help
+	  Compile in RBCPR (RapidBridge Core Power Reduction) driver to support
+	  corner vote for APC power rail. The driver takes PTE process voltage
+	  suggestions in efuse as initial settings. It converts corner vote
+	  to voltage value before writing to a voltage regulator API, such as
+	  that provided by spm-regulator driver.
+
 config REGULATOR_CPR3
 	bool "CPR3 regulator core support"
 	help
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index b2bfba8..c75e399 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -116,6 +116,7 @@
 obj-$(CONFIG_REGULATOR_RPM_SMD) += rpm-smd-regulator.o
 obj-$(CONFIG_REGULATOR_SPM) += spm-regulator.o
 
+obj-$(CONFIG_REGULATOR_CPR) += cpr-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
diff --git a/drivers/regulator/cpr-regulator.c b/drivers/regulator/cpr-regulator.c
new file mode 100644
index 0000000..9c47e82
--- /dev/null
+++ b/drivers/regulator/cpr-regulator.c
@@ -0,0 +1,6408 @@
+/*
+ * Copyright (c) 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.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/cpu.h>
+#include <linux/cpu_pm.h>
+#include <linux/cpumask.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_opp.h>
+#include <linux/interrupt.h>
+#include <linux/debugfs.h>
+#include <linux/sort.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/cpr-regulator.h>
+#include <linux/msm_thermal.h>
+#include <linux/msm_tsens.h>
+#include <soc/qcom/scm.h>
+
+/* Register Offsets for RB-CPR and Bit Definitions */
+
+/* RBCPR Version Register */
+#define REG_RBCPR_VERSION		0
+#define RBCPR_VER_2			0x02
+
+/* RBCPR Gate Count and Target Registers */
+#define REG_RBCPR_GCNT_TARGET(n)	(0x60 + 4 * n)
+
+#define RBCPR_GCNT_TARGET_GCNT_BITS	10
+#define RBCPR_GCNT_TARGET_GCNT_SHIFT	12
+#define RBCPR_GCNT_TARGET_GCNT_MASK	((1<<RBCPR_GCNT_TARGET_GCNT_BITS)-1)
+
+/* RBCPR Sensor Mask and Bypass Registers */
+#define REG_RBCPR_SENSOR_MASK0		0x20
+#define RBCPR_SENSOR_MASK0_SENSOR(n)	(~BIT(n))
+#define REG_RBCPR_SENSOR_BYPASS0	0x30
+
+/* RBCPR Timer Control */
+#define REG_RBCPR_TIMER_INTERVAL	0x44
+#define REG_RBIF_TIMER_ADJUST		0x4C
+
+#define RBIF_TIMER_ADJ_CONS_UP_BITS	4
+#define RBIF_TIMER_ADJ_CONS_UP_MASK	((1<<RBIF_TIMER_ADJ_CONS_UP_BITS)-1)
+#define RBIF_TIMER_ADJ_CONS_DOWN_BITS	4
+#define RBIF_TIMER_ADJ_CONS_DOWN_MASK	((1<<RBIF_TIMER_ADJ_CONS_DOWN_BITS)-1)
+#define RBIF_TIMER_ADJ_CONS_DOWN_SHIFT	4
+#define RBIF_TIMER_ADJ_CLAMP_INT_BITS	8
+#define RBIF_TIMER_ADJ_CLAMP_INT_MASK	((1<<RBIF_TIMER_ADJ_CLAMP_INT_BITS)-1)
+#define RBIF_TIMER_ADJ_CLAMP_INT_SHIFT	8
+
+/* RBCPR Config Register */
+#define REG_RBIF_LIMIT			0x48
+#define REG_RBCPR_STEP_QUOT		0x80
+#define REG_RBIF_SW_VLEVEL		0x94
+
+#define RBIF_LIMIT_CEILING_BITS		6
+#define RBIF_LIMIT_CEILING_MASK		((1<<RBIF_LIMIT_CEILING_BITS)-1)
+#define RBIF_LIMIT_CEILING_SHIFT	6
+#define RBIF_LIMIT_FLOOR_BITS		6
+#define RBIF_LIMIT_FLOOR_MASK		((1<<RBIF_LIMIT_FLOOR_BITS)-1)
+
+#define RBIF_LIMIT_CEILING_DEFAULT	RBIF_LIMIT_CEILING_MASK
+#define RBIF_LIMIT_FLOOR_DEFAULT	0
+#define RBIF_SW_VLEVEL_DEFAULT		0x20
+
+#define RBCPR_STEP_QUOT_STEPQUOT_BITS	8
+#define RBCPR_STEP_QUOT_STEPQUOT_MASK	((1<<RBCPR_STEP_QUOT_STEPQUOT_BITS)-1)
+#define RBCPR_STEP_QUOT_IDLE_CLK_BITS	4
+#define RBCPR_STEP_QUOT_IDLE_CLK_MASK	((1<<RBCPR_STEP_QUOT_IDLE_CLK_BITS)-1)
+#define RBCPR_STEP_QUOT_IDLE_CLK_SHIFT	8
+
+/* RBCPR Control Register */
+#define REG_RBCPR_CTL			0x90
+
+#define RBCPR_CTL_LOOP_EN			BIT(0)
+#define RBCPR_CTL_TIMER_EN			BIT(3)
+#define RBCPR_CTL_SW_AUTO_CONT_ACK_EN		BIT(5)
+#define RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN	BIT(6)
+#define RBCPR_CTL_COUNT_MODE			BIT(10)
+#define RBCPR_CTL_UP_THRESHOLD_BITS	4
+#define RBCPR_CTL_UP_THRESHOLD_MASK	((1<<RBCPR_CTL_UP_THRESHOLD_BITS)-1)
+#define RBCPR_CTL_UP_THRESHOLD_SHIFT	24
+#define RBCPR_CTL_DN_THRESHOLD_BITS	4
+#define RBCPR_CTL_DN_THRESHOLD_MASK	((1<<RBCPR_CTL_DN_THRESHOLD_BITS)-1)
+#define RBCPR_CTL_DN_THRESHOLD_SHIFT	28
+
+/* RBCPR Ack/Nack Response */
+#define REG_RBIF_CONT_ACK_CMD		0x98
+#define REG_RBIF_CONT_NACK_CMD		0x9C
+
+/* RBCPR Result status Registers */
+#define REG_RBCPR_RESULT_0		0xA0
+#define REG_RBCPR_RESULT_1		0xA4
+
+#define RBCPR_RESULT_1_SEL_FAST_BITS	3
+#define RBCPR_RESULT_1_SEL_FAST(val)	(val & \
+					((1<<RBCPR_RESULT_1_SEL_FAST_BITS) - 1))
+
+#define RBCPR_RESULT0_BUSY_SHIFT	19
+#define RBCPR_RESULT0_BUSY_MASK		BIT(RBCPR_RESULT0_BUSY_SHIFT)
+#define RBCPR_RESULT0_ERROR_LT0_SHIFT	18
+#define RBCPR_RESULT0_ERROR_SHIFT	6
+#define RBCPR_RESULT0_ERROR_BITS	12
+#define RBCPR_RESULT0_ERROR_MASK	((1<<RBCPR_RESULT0_ERROR_BITS)-1)
+#define RBCPR_RESULT0_ERROR_STEPS_SHIFT	2
+#define RBCPR_RESULT0_ERROR_STEPS_BITS	4
+#define RBCPR_RESULT0_ERROR_STEPS_MASK	((1<<RBCPR_RESULT0_ERROR_STEPS_BITS)-1)
+#define RBCPR_RESULT0_STEP_UP_SHIFT	1
+
+/* RBCPR Interrupt Control Register */
+#define REG_RBIF_IRQ_EN(n)		(0x100 + 4 * n)
+#define REG_RBIF_IRQ_CLEAR		0x110
+#define REG_RBIF_IRQ_STATUS		0x114
+
+#define CPR_INT_DONE		BIT(0)
+#define CPR_INT_MIN		BIT(1)
+#define CPR_INT_DOWN		BIT(2)
+#define CPR_INT_MID		BIT(3)
+#define CPR_INT_UP		BIT(4)
+#define CPR_INT_MAX		BIT(5)
+#define CPR_INT_CLAMP		BIT(6)
+#define CPR_INT_ALL	(CPR_INT_DONE | CPR_INT_MIN | CPR_INT_DOWN | \
+			CPR_INT_MID | CPR_INT_UP | CPR_INT_MAX | CPR_INT_CLAMP)
+#define CPR_INT_DEFAULT	(CPR_INT_UP | CPR_INT_DOWN)
+
+#define CPR_NUM_RING_OSC	8
+
+/* RBCPR Debug Resgister */
+#define REG_RBCPR_DEBUG1		0x120
+#define RBCPR_DEBUG1_QUOT_FAST_BITS	12
+#define RBCPR_DEBUG1_QUOT_SLOW_BITS	12
+#define RBCPR_DEBUG1_QUOT_SLOW_SHIFT	12
+
+#define RBCPR_DEBUG1_QUOT_FAST(val)	(val & \
+					((1<<RBCPR_DEBUG1_QUOT_FAST_BITS)-1))
+
+#define RBCPR_DEBUG1_QUOT_SLOW(val)	((val>>RBCPR_DEBUG1_QUOT_SLOW_SHIFT) & \
+					((1<<RBCPR_DEBUG1_QUOT_SLOW_BITS)-1))
+
+/* RBCPR Aging Resgister */
+#define REG_RBCPR_HTOL_AGE		0x160
+#define RBCPR_HTOL_AGE_PAGE		BIT(1)
+#define RBCPR_AGE_DATA_STATUS		BIT(2)
+
+/* RBCPR Clock Control Register */
+#define RBCPR_CLK_SEL_MASK	BIT(0)
+#define RBCPR_CLK_SEL_19P2_MHZ	0
+#define RBCPR_CLK_SEL_AHB_CLK	BIT(0)
+
+/* CPR eFuse parameters */
+#define CPR_FUSE_TARGET_QUOT_BITS	12
+#define CPR_FUSE_TARGET_QUOT_BITS_MASK	((1<<CPR_FUSE_TARGET_QUOT_BITS)-1)
+#define CPR_FUSE_RO_SEL_BITS		3
+#define CPR_FUSE_RO_SEL_BITS_MASK	((1<<CPR_FUSE_RO_SEL_BITS)-1)
+
+#define CPR_FUSE_MIN_QUOT_DIFF		50
+
+#define BYTES_PER_FUSE_ROW		8
+
+#define SPEED_BIN_NONE			UINT_MAX
+
+#define FUSE_REVISION_UNKNOWN		(-1)
+#define FUSE_MAP_NO_MATCH		(-1)
+#define FUSE_PARAM_MATCH_ANY		0xFFFFFFFF
+
+#define FLAGS_IGNORE_1ST_IRQ_STATUS	BIT(0)
+#define FLAGS_SET_MIN_VOLTAGE		BIT(1)
+#define FLAGS_UPLIFT_QUOT_VOLT		BIT(2)
+
+/*
+ * The number of individual aging measurements to perform which are then
+ * averaged together in order to determine the final aging adjustment value.
+ */
+#define CPR_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 CPR_AGING_MEASUREMENT_FILTER	3
+
+#define CPR_REGULATOR_DRIVER_NAME	"qcom,cpr-regulator"
+
+/**
+ * enum vdd_mx_vmin_method - Method to determine vmin for vdd-mx
+ * %VDD_MX_VMIN_APC:			Equal to APC voltage
+ * %VDD_MX_VMIN_APC_CORNER_CEILING:	Equal to PVS corner ceiling voltage
+ * %VDD_MX_VMIN_APC_SLOW_CORNER_CEILING:
+ *					Equal to slow speed corner ceiling
+ * %VDD_MX_VMIN_MX_VMAX:		Equal to specified vdd-mx-vmax voltage
+ * %VDD_MX_VMIN_APC_CORNER_MAP:		Equal to the APC corner mapped MX
+ *					voltage
+ */
+enum vdd_mx_vmin_method {
+	VDD_MX_VMIN_APC,
+	VDD_MX_VMIN_APC_CORNER_CEILING,
+	VDD_MX_VMIN_APC_SLOW_CORNER_CEILING,
+	VDD_MX_VMIN_MX_VMAX,
+	VDD_MX_VMIN_APC_FUSE_CORNER_MAP,
+	VDD_MX_VMIN_APC_CORNER_MAP,
+};
+
+#define CPR_CORNER_MIN		1
+#define CPR_FUSE_CORNER_MIN	1
+/*
+ * This is an arbitrary upper limit which is used in a sanity check in order to
+ * avoid excessive memory allocation due to bad device tree data.
+ */
+#define CPR_FUSE_CORNER_LIMIT	100
+
+struct quot_adjust_info {
+	int speed_bin;
+	int virtual_corner;
+	int quot_adjust;
+};
+
+struct cpr_quot_scale {
+	u32 offset;
+	u32 multiplier;
+};
+
+struct cpr_aging_sensor_info {
+	u32 sensor_id;
+	int initial_quot_diff;
+	int current_quot_diff;
+};
+
+struct cpr_aging_info {
+	struct cpr_aging_sensor_info *sensor_info;
+	int	num_aging_sensors;
+	int	aging_corner;
+	u32	aging_ro_kv;
+	u32	*aging_derate;
+	u32	aging_sensor_bypass;
+	u32	max_aging_margin;
+	u32	aging_ref_voltage;
+	u32	cpr_ro_kv[CPR_NUM_RING_OSC];
+	int	*voltage_adjust;
+
+	bool	cpr_aging_error;
+	bool	cpr_aging_done;
+};
+
+static const char * const vdd_apc_name[] =	{"vdd-apc-optional-prim",
+						"vdd-apc-optional-sec",
+						"vdd-apc"};
+
+enum voltage_change_dir {
+	NO_CHANGE,
+	DOWN,
+	UP,
+};
+
+struct cpr_regulator {
+	struct list_head		list;
+	struct regulator_desc		rdesc;
+	struct regulator_dev		*rdev;
+	bool				vreg_enabled;
+	int				corner;
+	int				ceiling_max;
+	struct dentry			*debugfs;
+	struct device			*dev;
+
+	/* eFuse parameters */
+	phys_addr_t	efuse_addr;
+	void __iomem	*efuse_base;
+	u64		*remapped_row;
+	u32		remapped_row_base;
+	int		num_remapped_rows;
+
+	/* Process voltage parameters */
+	u32		*pvs_corner_v;
+	/* Process voltage variables */
+	u32		pvs_bin;
+	u32		speed_bin;
+	u32		pvs_version;
+
+	/* APC voltage regulator */
+	struct regulator	*vdd_apc;
+
+	/* Dependency parameters */
+	struct regulator	*vdd_mx;
+	int			vdd_mx_vmax;
+	int			vdd_mx_vmin_method;
+	int			vdd_mx_vmin;
+	int			*vdd_mx_corner_map;
+
+	struct regulator	*rpm_apc_vreg;
+	int			*rpm_apc_corner_map;
+
+	/* mem-acc regulator */
+	struct regulator	*mem_acc_vreg;
+
+	/* thermal monitor */
+	int			tsens_id;
+	int			cpr_disable_temp_threshold;
+	int			cpr_enable_temp_threshold;
+	bool			cpr_disable_on_temperature;
+	bool			cpr_thermal_disable;
+	struct threshold_info	tsens_threshold_config;
+
+	/* CPR parameters */
+	u32		num_fuse_corners;
+	u64		cpr_fuse_bits;
+	bool		cpr_fuse_disable;
+	bool		cpr_fuse_local;
+	bool		cpr_fuse_redundant;
+	int		cpr_fuse_revision;
+	int		cpr_fuse_map_count;
+	int		cpr_fuse_map_match;
+	int		*cpr_fuse_target_quot;
+	int		*cpr_fuse_ro_sel;
+	int		*fuse_quot_offset;
+	int		gcnt;
+
+	unsigned int	cpr_irq;
+	void __iomem	*rbcpr_base;
+	phys_addr_t	rbcpr_clk_addr;
+	struct mutex	cpr_mutex;
+
+	int		*cpr_max_ceiling;
+	int		*ceiling_volt;
+	int		*floor_volt;
+	int		*fuse_ceiling_volt;
+	int		*fuse_floor_volt;
+	int		*last_volt;
+	int		*open_loop_volt;
+	int		step_volt;
+
+	int		*save_ctl;
+	int		*save_irq;
+
+	int		*vsens_corner_map;
+	/* vsens status */
+	bool		vsens_enabled;
+	/* vsens regulators */
+	struct regulator	*vdd_vsens_corner;
+	struct regulator	*vdd_vsens_voltage;
+
+	/* Config parameters */
+	bool		enable;
+	u32		ref_clk_khz;
+	u32		timer_delay_us;
+	u32		timer_cons_up;
+	u32		timer_cons_down;
+	u32		irq_line;
+	u32		*step_quotient;
+	u32		up_threshold;
+	u32		down_threshold;
+	u32		idle_clocks;
+	u32		gcnt_time_us;
+	u32		clamp_timer_interval;
+	u32		vdd_apc_step_up_limit;
+	u32		vdd_apc_step_down_limit;
+	u32		flags;
+	int		*corner_map;
+	u32		num_corners;
+	int		*quot_adjust;
+	int		*mem_acc_corner_map;
+
+	int			num_adj_cpus;
+	int			online_cpus;
+	int			*adj_cpus;
+	int			**adj_cpus_save_ctl;
+	int			**adj_cpus_save_irq;
+	int			**adj_cpus_last_volt;
+	int			**adj_cpus_quot_adjust;
+	int			**adj_cpus_open_loop_volt;
+	bool			adj_cpus_open_loop_volt_as_ceiling;
+	struct notifier_block	cpu_notifier;
+	cpumask_t		cpu_mask;
+	bool			cpr_disabled_in_pc;
+	struct notifier_block	pm_notifier;
+
+	bool		is_cpr_suspended;
+	bool		skip_voltage_change_during_suspend;
+
+	struct cpr_aging_info	*aging_info;
+
+	struct notifier_block	panic_notifier;
+};
+
+#define CPR_DEBUG_MASK_IRQ	BIT(0)
+#define CPR_DEBUG_MASK_API	BIT(1)
+
+static int cpr_debug_enable;
+#if defined(CONFIG_DEBUG_FS)
+static struct dentry *cpr_debugfs_base;
+#endif
+
+static DEFINE_MUTEX(cpr_regulator_list_mutex);
+static LIST_HEAD(cpr_regulator_list);
+
+module_param_named(debug_enable, cpr_debug_enable, int, S_IRUGO | S_IWUSR);
+#define cpr_debug(cpr_vreg, message, ...) \
+	do { \
+		if (cpr_debug_enable & CPR_DEBUG_MASK_API) \
+			pr_info("%s: " message, (cpr_vreg)->rdesc.name, \
+				##__VA_ARGS__); \
+	} while (0)
+#define cpr_debug_irq(cpr_vreg, message, ...) \
+	do { \
+		if (cpr_debug_enable & CPR_DEBUG_MASK_IRQ) \
+			pr_info("%s: " message, (cpr_vreg)->rdesc.name, \
+				##__VA_ARGS__); \
+		else \
+			pr_debug("%s: " message, (cpr_vreg)->rdesc.name, \
+				##__VA_ARGS__); \
+	} while (0)
+#define cpr_info(cpr_vreg, message, ...) \
+	pr_info("%s: " message, (cpr_vreg)->rdesc.name, ##__VA_ARGS__)
+#define cpr_err(cpr_vreg, message, ...) \
+	pr_err("%s: " message, (cpr_vreg)->rdesc.name, ##__VA_ARGS__)
+
+static u64 cpr_read_remapped_efuse_row(struct cpr_regulator *cpr_vreg,
+					u32 row_num)
+{
+	if (row_num - cpr_vreg->remapped_row_base
+			>= cpr_vreg->num_remapped_rows) {
+		cpr_err(cpr_vreg, "invalid row=%u, max remapped row=%u\n",
+			row_num, cpr_vreg->remapped_row_base
+					+ cpr_vreg->num_remapped_rows - 1);
+		return 0;
+	}
+
+	return cpr_vreg->remapped_row[row_num - cpr_vreg->remapped_row_base];
+}
+
+static u64 cpr_read_efuse_row(struct cpr_regulator *cpr_vreg, u32 row_num,
+				bool use_tz_api)
+{
+	int rc;
+	u64 efuse_bits;
+	struct scm_desc desc = {0};
+	struct cpr_read_req {
+		u32 row_address;
+		int addr_type;
+	} req;
+
+	struct cpr_read_rsp {
+		u32 row_data[2];
+		u32 status;
+	} rsp;
+
+	if (cpr_vreg->remapped_row && row_num >= cpr_vreg->remapped_row_base)
+		return cpr_read_remapped_efuse_row(cpr_vreg, row_num);
+
+	if (!use_tz_api) {
+		efuse_bits = readq_relaxed(cpr_vreg->efuse_base
+			+ row_num * BYTES_PER_FUSE_ROW);
+		return efuse_bits;
+	}
+
+	desc.args[0] = req.row_address = cpr_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) {
+		cpr_err(cpr_vreg, "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;
+}
+
+/**
+ * cpr_read_efuse_param() - read a parameter from one or two eFuse rows
+ * @cpr_vreg:	Pointer to cpr_regulator struct for this regulator.
+ * @row_start:	Fuse row number to start reading from.
+ * @bit_start:	The LSB of the parameter to read from the fuse.
+ * @bit_len:	The length of the parameter in bits.
+ * @use_tz_api:	Flag to indicate if an SCM call should be used to read the fuse.
+ *
+ * This function reads a parameter of specified offset and bit size out of one
+ * or two consecutive eFuse rows.  This allows for the reading of parameters
+ * that happen to be split between two eFuse rows.
+ *
+ * Returns the fuse parameter on success or 0 on failure.
+ */
+static u64 cpr_read_efuse_param(struct cpr_regulator *cpr_vreg, int row_start,
+		int bit_start, int bit_len, bool use_tz_api)
+{
+	u64 fuse[2];
+	u64 param = 0;
+	int bits_first, bits_second;
+
+	if (bit_start < 0) {
+		cpr_err(cpr_vreg, "Invalid LSB = %d specified\n", bit_start);
+		return 0;
+	}
+
+	if (bit_len < 0 || bit_len > 64) {
+		cpr_err(cpr_vreg, "Invalid bit length = %d specified\n",
+			bit_len);
+		return 0;
+	}
+
+	/* Allow bit indexing to start beyond the end of the start row. */
+	if (bit_start >= 64) {
+		row_start += bit_start >> 6; /* equivalent to bit_start / 64 */
+		bit_start &= 0x3F;
+	}
+
+	fuse[0] = cpr_read_efuse_row(cpr_vreg, row_start, use_tz_api);
+
+	if (bit_start == 0 && bit_len == 64) {
+		param = fuse[0];
+	} else if (bit_start + bit_len <= 64) {
+		param = (fuse[0] >> bit_start) & ((1ULL << bit_len) - 1);
+	} else {
+		fuse[1] = cpr_read_efuse_row(cpr_vreg, row_start + 1,
+						use_tz_api);
+		bits_first = 64 - bit_start;
+		bits_second = bit_len - bits_first;
+		param = (fuse[0] >> bit_start) & ((1ULL << bits_first) - 1);
+		param |= (fuse[1] & ((1ULL << bits_second) - 1)) << bits_first;
+	}
+
+	return param;
+}
+
+static bool cpr_is_allowed(struct cpr_regulator *cpr_vreg)
+{
+	if (cpr_vreg->cpr_fuse_disable || !cpr_vreg->enable ||
+				cpr_vreg->cpr_thermal_disable)
+		return false;
+	else
+		return true;
+}
+
+static void cpr_write(struct cpr_regulator *cpr_vreg, u32 offset, u32 value)
+{
+	writel_relaxed(value, cpr_vreg->rbcpr_base + offset);
+}
+
+static u32 cpr_read(struct cpr_regulator *cpr_vreg, u32 offset)
+{
+	return readl_relaxed(cpr_vreg->rbcpr_base + offset);
+}
+
+static void cpr_masked_write(struct cpr_regulator *cpr_vreg, u32 offset,
+			     u32 mask, u32 value)
+{
+	u32 reg_val;
+
+	reg_val = readl_relaxed(cpr_vreg->rbcpr_base + offset);
+	reg_val &= ~mask;
+	reg_val |= value & mask;
+	writel_relaxed(reg_val, cpr_vreg->rbcpr_base + offset);
+}
+
+static void cpr_irq_clr(struct cpr_regulator *cpr_vreg)
+{
+	cpr_write(cpr_vreg, REG_RBIF_IRQ_CLEAR, CPR_INT_ALL);
+}
+
+static void cpr_irq_clr_nack(struct cpr_regulator *cpr_vreg)
+{
+	cpr_irq_clr(cpr_vreg);
+	cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);
+}
+
+static void cpr_irq_clr_ack(struct cpr_regulator *cpr_vreg)
+{
+	cpr_irq_clr(cpr_vreg);
+	cpr_write(cpr_vreg, REG_RBIF_CONT_ACK_CMD, 1);
+}
+
+static void cpr_irq_set(struct cpr_regulator *cpr_vreg, u32 int_bits)
+{
+	cpr_write(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line), int_bits);
+}
+
+static void cpr_ctl_modify(struct cpr_regulator *cpr_vreg, u32 mask, u32 value)
+{
+	cpr_masked_write(cpr_vreg, REG_RBCPR_CTL, mask, value);
+}
+
+static void cpr_ctl_enable(struct cpr_regulator *cpr_vreg, int corner)
+{
+	u32 val;
+
+	if (cpr_vreg->is_cpr_suspended)
+		return;
+
+	/* Program Consecutive Up & Down */
+	val = ((cpr_vreg->timer_cons_down & RBIF_TIMER_ADJ_CONS_DOWN_MASK)
+			<< RBIF_TIMER_ADJ_CONS_DOWN_SHIFT) |
+		(cpr_vreg->timer_cons_up & RBIF_TIMER_ADJ_CONS_UP_MASK);
+	cpr_masked_write(cpr_vreg, REG_RBIF_TIMER_ADJUST,
+			RBIF_TIMER_ADJ_CONS_UP_MASK |
+			RBIF_TIMER_ADJ_CONS_DOWN_MASK, val);
+	cpr_masked_write(cpr_vreg, REG_RBCPR_CTL,
+			RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN |
+			RBCPR_CTL_SW_AUTO_CONT_ACK_EN,
+			cpr_vreg->save_ctl[corner]);
+	cpr_irq_set(cpr_vreg, cpr_vreg->save_irq[corner]);
+
+	if (cpr_is_allowed(cpr_vreg) && cpr_vreg->vreg_enabled &&
+	    (cpr_vreg->ceiling_volt[corner] >
+		cpr_vreg->floor_volt[corner]))
+		val = RBCPR_CTL_LOOP_EN;
+	else
+		val = 0;
+	cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, val);
+}
+
+static void cpr_ctl_disable(struct cpr_regulator *cpr_vreg)
+{
+	if (cpr_vreg->is_cpr_suspended)
+		return;
+
+	cpr_irq_set(cpr_vreg, 0);
+	cpr_ctl_modify(cpr_vreg, RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN |
+			RBCPR_CTL_SW_AUTO_CONT_ACK_EN, 0);
+	cpr_masked_write(cpr_vreg, REG_RBIF_TIMER_ADJUST,
+			RBIF_TIMER_ADJ_CONS_UP_MASK |
+			RBIF_TIMER_ADJ_CONS_DOWN_MASK, 0);
+	cpr_irq_clr(cpr_vreg);
+	cpr_write(cpr_vreg, REG_RBIF_CONT_ACK_CMD, 1);
+	cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);
+	cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0);
+}
+
+static bool cpr_ctl_is_enabled(struct cpr_regulator *cpr_vreg)
+{
+	u32 reg_val;
+
+	reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL);
+	return reg_val & RBCPR_CTL_LOOP_EN;
+}
+
+static bool cpr_ctl_is_busy(struct cpr_regulator *cpr_vreg)
+{
+	u32 reg_val;
+
+	reg_val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0);
+	return reg_val & RBCPR_RESULT0_BUSY_MASK;
+}
+
+static void cpr_corner_save(struct cpr_regulator *cpr_vreg, int corner)
+{
+	cpr_vreg->save_ctl[corner] = cpr_read(cpr_vreg, REG_RBCPR_CTL);
+	cpr_vreg->save_irq[corner] =
+		cpr_read(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line));
+}
+
+static void cpr_corner_restore(struct cpr_regulator *cpr_vreg, int corner)
+{
+	u32 gcnt, ctl, irq, ro_sel, step_quot;
+	int fuse_corner = cpr_vreg->corner_map[corner];
+	int i;
+
+	ro_sel = cpr_vreg->cpr_fuse_ro_sel[fuse_corner];
+	gcnt = cpr_vreg->gcnt | (cpr_vreg->cpr_fuse_target_quot[fuse_corner] -
+					cpr_vreg->quot_adjust[corner]);
+
+	/* Program the step quotient and idle clocks */
+	step_quot = ((cpr_vreg->idle_clocks & RBCPR_STEP_QUOT_IDLE_CLK_MASK)
+			<< RBCPR_STEP_QUOT_IDLE_CLK_SHIFT) |
+		(cpr_vreg->step_quotient[fuse_corner]
+			& RBCPR_STEP_QUOT_STEPQUOT_MASK);
+	cpr_write(cpr_vreg, REG_RBCPR_STEP_QUOT, step_quot);
+
+	/* Clear the target quotient value and gate count of all ROs */
+	for (i = 0; i < CPR_NUM_RING_OSC; i++)
+		cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0);
+
+	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(ro_sel), gcnt);
+	ctl = cpr_vreg->save_ctl[corner];
+	cpr_write(cpr_vreg, REG_RBCPR_CTL, ctl);
+	irq = cpr_vreg->save_irq[corner];
+	cpr_irq_set(cpr_vreg, irq);
+	cpr_debug(cpr_vreg, "gcnt = 0x%08x, ctl = 0x%08x, irq = 0x%08x\n",
+		  gcnt, ctl, irq);
+}
+
+static void cpr_corner_switch(struct cpr_regulator *cpr_vreg, int corner)
+{
+	if (cpr_vreg->corner == corner)
+		return;
+
+	cpr_corner_restore(cpr_vreg, corner);
+}
+
+static int cpr_apc_set(struct cpr_regulator *cpr_vreg, u32 new_volt)
+{
+	int max_volt, rc;
+
+	max_volt = cpr_vreg->ceiling_max;
+	rc = regulator_set_voltage(cpr_vreg->vdd_apc, new_volt, max_volt);
+	if (rc)
+		cpr_err(cpr_vreg, "set: vdd_apc = %d uV: rc=%d\n",
+			new_volt, rc);
+	return rc;
+}
+
+static int cpr_mx_get(struct cpr_regulator *cpr_vreg, int corner, int apc_volt)
+{
+	int vdd_mx;
+	int fuse_corner = cpr_vreg->corner_map[corner];
+	int highest_fuse_corner = cpr_vreg->num_fuse_corners;
+
+	switch (cpr_vreg->vdd_mx_vmin_method) {
+	case VDD_MX_VMIN_APC:
+		vdd_mx = apc_volt;
+		break;
+	case VDD_MX_VMIN_APC_CORNER_CEILING:
+		vdd_mx = cpr_vreg->fuse_ceiling_volt[fuse_corner];
+		break;
+	case VDD_MX_VMIN_APC_SLOW_CORNER_CEILING:
+		vdd_mx = cpr_vreg->fuse_ceiling_volt[highest_fuse_corner];
+		break;
+	case VDD_MX_VMIN_MX_VMAX:
+		vdd_mx = cpr_vreg->vdd_mx_vmax;
+		break;
+	case VDD_MX_VMIN_APC_FUSE_CORNER_MAP:
+		vdd_mx = cpr_vreg->vdd_mx_corner_map[fuse_corner];
+		break;
+	case VDD_MX_VMIN_APC_CORNER_MAP:
+		vdd_mx = cpr_vreg->vdd_mx_corner_map[corner];
+		break;
+	default:
+		vdd_mx = 0;
+		break;
+	}
+
+	return vdd_mx;
+}
+
+static int cpr_mx_set(struct cpr_regulator *cpr_vreg, int corner,
+		      int vdd_mx_vmin)
+{
+	int rc;
+	int fuse_corner = cpr_vreg->corner_map[corner];
+
+	rc = regulator_set_voltage(cpr_vreg->vdd_mx, vdd_mx_vmin,
+				   cpr_vreg->vdd_mx_vmax);
+	cpr_debug(cpr_vreg, "[corner:%d, fuse_corner:%d] %d uV\n", corner,
+			fuse_corner, vdd_mx_vmin);
+
+	if (!rc) {
+		cpr_vreg->vdd_mx_vmin = vdd_mx_vmin;
+	} else {
+		cpr_err(cpr_vreg, "set: vdd_mx [corner:%d, fuse_corner:%d] = %d uV failed: rc=%d\n",
+			corner, fuse_corner, vdd_mx_vmin, rc);
+	}
+	return rc;
+}
+
+static int cpr_scale_voltage(struct cpr_regulator *cpr_vreg, int corner,
+			     int new_apc_volt, enum voltage_change_dir dir)
+{
+	int rc = 0, vdd_mx_vmin = 0;
+	int mem_acc_corner = cpr_vreg->mem_acc_corner_map[corner];
+	int fuse_corner = cpr_vreg->corner_map[corner];
+	int apc_corner, vsens_corner;
+
+	/* Determine the vdd_mx voltage */
+	if (dir != NO_CHANGE && cpr_vreg->vdd_mx != NULL)
+		vdd_mx_vmin = cpr_mx_get(cpr_vreg, corner, new_apc_volt);
+
+
+	if (cpr_vreg->vdd_vsens_voltage && cpr_vreg->vsens_enabled) {
+		rc = regulator_disable(cpr_vreg->vdd_vsens_voltage);
+		if (!rc)
+			cpr_vreg->vsens_enabled = false;
+	}
+
+	if (dir == DOWN) {
+		if (!rc && cpr_vreg->mem_acc_vreg)
+			rc = regulator_set_voltage(cpr_vreg->mem_acc_vreg,
+					mem_acc_corner, mem_acc_corner);
+		if (!rc && cpr_vreg->rpm_apc_vreg) {
+			apc_corner = cpr_vreg->rpm_apc_corner_map[corner];
+			rc = regulator_set_voltage(cpr_vreg->rpm_apc_vreg,
+						apc_corner, apc_corner);
+			if (rc)
+				cpr_err(cpr_vreg, "apc_corner voting failed rc=%d\n",
+						rc);
+		}
+	}
+
+	if (!rc && vdd_mx_vmin && dir == UP) {
+		if (vdd_mx_vmin != cpr_vreg->vdd_mx_vmin)
+			rc = cpr_mx_set(cpr_vreg, corner, vdd_mx_vmin);
+	}
+
+	if (!rc)
+		rc = cpr_apc_set(cpr_vreg, new_apc_volt);
+
+	if (dir == UP) {
+		if (!rc && cpr_vreg->mem_acc_vreg)
+			rc = regulator_set_voltage(cpr_vreg->mem_acc_vreg,
+					mem_acc_corner, mem_acc_corner);
+		if (!rc && cpr_vreg->rpm_apc_vreg) {
+			apc_corner = cpr_vreg->rpm_apc_corner_map[corner];
+			rc = regulator_set_voltage(cpr_vreg->rpm_apc_vreg,
+						apc_corner, apc_corner);
+			if (rc)
+				cpr_err(cpr_vreg, "apc_corner voting failed rc=%d\n",
+						rc);
+		}
+	}
+
+	if (!rc && vdd_mx_vmin && dir == DOWN) {
+		if (vdd_mx_vmin != cpr_vreg->vdd_mx_vmin)
+			rc = cpr_mx_set(cpr_vreg, corner, vdd_mx_vmin);
+	}
+
+	if (!rc && cpr_vreg->vdd_vsens_corner) {
+		vsens_corner = cpr_vreg->vsens_corner_map[fuse_corner];
+		rc = regulator_set_voltage(cpr_vreg->vdd_vsens_corner,
+					vsens_corner, vsens_corner);
+	}
+	if (!rc && cpr_vreg->vdd_vsens_voltage) {
+		rc = regulator_set_voltage(cpr_vreg->vdd_vsens_voltage,
+					cpr_vreg->floor_volt[corner],
+					cpr_vreg->ceiling_volt[corner]);
+		if (!rc && !cpr_vreg->vsens_enabled) {
+			rc = regulator_enable(cpr_vreg->vdd_vsens_voltage);
+			if (!rc)
+				cpr_vreg->vsens_enabled = true;
+		}
+	}
+
+	return rc;
+}
+
+static void cpr_scale(struct cpr_regulator *cpr_vreg,
+		      enum voltage_change_dir dir)
+{
+	u32 reg_val, error_steps, reg_mask;
+	int last_volt, new_volt, corner, fuse_corner;
+	u32 gcnt, quot;
+
+	corner = cpr_vreg->corner;
+	fuse_corner = cpr_vreg->corner_map[corner];
+
+	reg_val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0);
+
+	error_steps = (reg_val >> RBCPR_RESULT0_ERROR_STEPS_SHIFT)
+				& RBCPR_RESULT0_ERROR_STEPS_MASK;
+	last_volt = cpr_vreg->last_volt[corner];
+
+	cpr_debug_irq(cpr_vreg,
+			"last_volt[corner:%d, fuse_corner:%d] = %d uV\n",
+			corner, fuse_corner, last_volt);
+
+	gcnt = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET
+			(cpr_vreg->cpr_fuse_ro_sel[fuse_corner]));
+	quot = gcnt & ((1 << RBCPR_GCNT_TARGET_GCNT_SHIFT) - 1);
+
+	if (dir == UP) {
+		if (cpr_vreg->clamp_timer_interval
+				&& error_steps < cpr_vreg->up_threshold) {
+			/*
+			 * Handle the case where another measurement started
+			 * after the interrupt was triggered due to a core
+			 * exiting from power collapse.
+			 */
+			error_steps = max(cpr_vreg->up_threshold,
+					cpr_vreg->vdd_apc_step_up_limit);
+		}
+		cpr_debug_irq(cpr_vreg,
+				"Up: cpr status = 0x%08x (error_steps=%d)\n",
+				reg_val, error_steps);
+
+		if (last_volt >= cpr_vreg->ceiling_volt[corner]) {
+			cpr_debug_irq(cpr_vreg,
+			"[corn:%d, fuse_corn:%d] @ ceiling: %d >= %d: NACK\n",
+				corner, fuse_corner, last_volt,
+				cpr_vreg->ceiling_volt[corner]);
+			cpr_irq_clr_nack(cpr_vreg);
+
+			cpr_debug_irq(cpr_vreg, "gcnt = 0x%08x (quot = %d)\n",
+					gcnt, quot);
+
+			/* Maximize the UP threshold */
+			reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK <<
+					RBCPR_CTL_UP_THRESHOLD_SHIFT;
+			reg_val = reg_mask;
+			cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
+
+			/* Disable UP interrupt */
+			cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT & ~CPR_INT_UP);
+
+			return;
+		}
+
+		if (error_steps > cpr_vreg->vdd_apc_step_up_limit) {
+			cpr_debug_irq(cpr_vreg,
+				      "%d is over up-limit(%d): Clamp\n",
+				      error_steps,
+				      cpr_vreg->vdd_apc_step_up_limit);
+			error_steps = cpr_vreg->vdd_apc_step_up_limit;
+		}
+
+		/* Calculate new voltage */
+		new_volt = last_volt + (error_steps * cpr_vreg->step_volt);
+		if (new_volt > cpr_vreg->ceiling_volt[corner]) {
+			cpr_debug_irq(cpr_vreg,
+				      "new_volt(%d) >= ceiling(%d): Clamp\n",
+				      new_volt,
+				      cpr_vreg->ceiling_volt[corner]);
+
+			new_volt = cpr_vreg->ceiling_volt[corner];
+		}
+
+		if (cpr_scale_voltage(cpr_vreg, corner, new_volt, dir)) {
+			cpr_irq_clr_nack(cpr_vreg);
+			return;
+		}
+		cpr_vreg->last_volt[corner] = new_volt;
+
+		/* Disable auto nack down */
+		reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN;
+		reg_val = 0;
+
+		cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
+
+		/* Re-enable default interrupts */
+		cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT);
+
+		/* Ack */
+		cpr_irq_clr_ack(cpr_vreg);
+
+		cpr_debug_irq(cpr_vreg,
+			"UP: -> new_volt[corner:%d, fuse_corner:%d] = %d uV\n",
+			corner, fuse_corner, new_volt);
+	} else if (dir == DOWN) {
+		if (cpr_vreg->clamp_timer_interval
+				&& error_steps < cpr_vreg->down_threshold) {
+			/*
+			 * Handle the case where another measurement started
+			 * after the interrupt was triggered due to a core
+			 * exiting from power collapse.
+			 */
+			error_steps = max(cpr_vreg->down_threshold,
+					cpr_vreg->vdd_apc_step_down_limit);
+		}
+		cpr_debug_irq(cpr_vreg,
+			      "Down: cpr status = 0x%08x (error_steps=%d)\n",
+			      reg_val, error_steps);
+
+		if (last_volt <= cpr_vreg->floor_volt[corner]) {
+			cpr_debug_irq(cpr_vreg,
+			"[corn:%d, fuse_corner:%d] @ floor: %d <= %d: NACK\n",
+				corner, fuse_corner, last_volt,
+				cpr_vreg->floor_volt[corner]);
+			cpr_irq_clr_nack(cpr_vreg);
+
+			cpr_debug_irq(cpr_vreg, "gcnt = 0x%08x (quot = %d)\n",
+					gcnt, quot);
+
+			/* Enable auto nack down */
+			reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN;
+			reg_val = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN;
+
+			cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
+
+			/* Disable DOWN interrupt */
+			cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT & ~CPR_INT_DOWN);
+
+			return;
+		}
+
+		if (error_steps > cpr_vreg->vdd_apc_step_down_limit) {
+			cpr_debug_irq(cpr_vreg,
+				      "%d is over down-limit(%d): Clamp\n",
+				      error_steps,
+				      cpr_vreg->vdd_apc_step_down_limit);
+			error_steps = cpr_vreg->vdd_apc_step_down_limit;
+		}
+
+		/* Calculte new voltage */
+		new_volt = last_volt - (error_steps * cpr_vreg->step_volt);
+		if (new_volt < cpr_vreg->floor_volt[corner]) {
+			cpr_debug_irq(cpr_vreg,
+				      "new_volt(%d) < floor(%d): Clamp\n",
+				      new_volt,
+				      cpr_vreg->floor_volt[corner]);
+			new_volt = cpr_vreg->floor_volt[corner];
+		}
+
+		if (cpr_scale_voltage(cpr_vreg, corner, new_volt, dir)) {
+			cpr_irq_clr_nack(cpr_vreg);
+			return;
+		}
+		cpr_vreg->last_volt[corner] = new_volt;
+
+		/* Restore default threshold for UP */
+		reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK <<
+				RBCPR_CTL_UP_THRESHOLD_SHIFT;
+		reg_val = cpr_vreg->up_threshold <<
+				RBCPR_CTL_UP_THRESHOLD_SHIFT;
+		cpr_ctl_modify(cpr_vreg, reg_mask, reg_val);
+
+		/* Re-enable default interrupts */
+		cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT);
+
+		/* Ack */
+		cpr_irq_clr_ack(cpr_vreg);
+
+		cpr_debug_irq(cpr_vreg,
+		"DOWN: -> new_volt[corner:%d, fuse_corner:%d] = %d uV\n",
+			corner, fuse_corner, new_volt);
+	}
+}
+
+static irqreturn_t cpr_irq_handler(int irq, void *dev)
+{
+	struct cpr_regulator *cpr_vreg = dev;
+	u32 reg_val;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	reg_val = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS);
+	if (cpr_vreg->flags & FLAGS_IGNORE_1ST_IRQ_STATUS)
+		reg_val = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS);
+
+	cpr_debug_irq(cpr_vreg, "IRQ_STATUS = 0x%02X\n", reg_val);
+
+	if (!cpr_ctl_is_enabled(cpr_vreg)) {
+		cpr_debug_irq(cpr_vreg, "CPR is disabled\n");
+		goto _exit;
+	} else if (cpr_ctl_is_busy(cpr_vreg)
+			&& !cpr_vreg->clamp_timer_interval) {
+		cpr_debug_irq(cpr_vreg, "CPR measurement is not ready\n");
+		goto _exit;
+	} else if (!cpr_is_allowed(cpr_vreg)) {
+		reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL);
+		cpr_err(cpr_vreg, "Interrupt broken? RBCPR_CTL = 0x%02X\n",
+			reg_val);
+		goto _exit;
+	}
+
+	/* Following sequence of handling is as per each IRQ's priority */
+	if (reg_val & CPR_INT_UP) {
+		cpr_scale(cpr_vreg, UP);
+	} else if (reg_val & CPR_INT_DOWN) {
+		cpr_scale(cpr_vreg, DOWN);
+	} else if (reg_val & CPR_INT_MIN) {
+		cpr_irq_clr_nack(cpr_vreg);
+	} else if (reg_val & CPR_INT_MAX) {
+		cpr_irq_clr_nack(cpr_vreg);
+	} else if (reg_val & CPR_INT_MID) {
+		/* RBCPR_CTL_SW_AUTO_CONT_ACK_EN is enabled */
+		cpr_debug_irq(cpr_vreg, "IRQ occurred for Mid Flag\n");
+	} else {
+		cpr_debug_irq(cpr_vreg,
+			"IRQ occurred for unknown flag (0x%08x)\n", reg_val);
+	}
+
+	/* Save register values for the corner */
+	cpr_corner_save(cpr_vreg, cpr_vreg->corner);
+
+_exit:
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+	return IRQ_HANDLED;
+}
+
+/**
+ * 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;
+}
+
+static int cpr_get_aging_quot_delta(struct cpr_regulator *cpr_vreg,
+			struct cpr_aging_sensor_info *aging_sensor_info)
+{
+	int quot_min, quot_max, is_aging_measurement, aging_measurement_count;
+	int quot_min_scaled, quot_max_scaled, quot_delta_scaled_sum;
+	int retries, rc = 0, sel_fast = 0, i, quot_delta_scaled;
+	u32 val, gcnt_ref, gcnt;
+	int *quot_delta_results, filtered_count;
+
+
+	quot_delta_results = kcalloc(CPR_AGING_MEASUREMENT_ITERATIONS,
+			sizeof(*quot_delta_results), GFP_ATOMIC);
+	if (!quot_delta_results)
+		return -ENOMEM;
+
+	/* Clear the target quotient value and gate count of all ROs */
+	for (i = 0; i < CPR_NUM_RING_OSC; i++)
+		cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0);
+
+	/* Program GCNT0/1 for getting aging data */
+	gcnt_ref = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000;
+	gcnt = gcnt_ref * 3 / 2;
+	val = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) <<
+			RBCPR_GCNT_TARGET_GCNT_SHIFT;
+	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), val);
+	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), val);
+
+	val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(0));
+	cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET0 = 0x%08x\n", val);
+
+	val = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(1));
+	cpr_debug(cpr_vreg, "RBCPR_GCNT_TARGET1 = 0x%08x\n", val);
+
+	/* Program TIMER_INTERVAL to zero */
+	cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, 0);
+
+	/* Bypass sensors in collapsible domain */
+	if (cpr_vreg->aging_info->aging_sensor_bypass)
+		cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0,
+			(cpr_vreg->aging_info->aging_sensor_bypass &
+		RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id)));
+
+	/* Mask other sensors */
+	cpr_write(cpr_vreg, REG_RBCPR_SENSOR_MASK0,
+		RBCPR_SENSOR_MASK0_SENSOR(aging_sensor_info->sensor_id));
+	val = cpr_read(cpr_vreg, REG_RBCPR_SENSOR_MASK0);
+	cpr_debug(cpr_vreg, "RBCPR_SENSOR_MASK0 = 0x%08x\n", val);
+
+	/* Enable cpr controller */
+	cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, RBCPR_CTL_LOOP_EN);
+
+	/* Make sure cpr starts measurement with toggling busy bit */
+	mb();
+
+	/* Wait and Ignore the first measurement. Time-out after 5ms */
+	retries = 50;
+	while (retries-- && cpr_ctl_is_busy(cpr_vreg))
+		udelay(100);
+
+	if (retries < 0) {
+		cpr_err(cpr_vreg, "Aging calibration failed\n");
+		rc = -EBUSY;
+		goto _exit;
+	}
+
+	/* Set age page mode */
+	cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, RBCPR_HTOL_AGE_PAGE);
+
+	aging_measurement_count = 0;
+	quot_delta_scaled_sum = 0;
+
+	for (i = 0; i < CPR_AGING_MEASUREMENT_ITERATIONS; i++) {
+		/* Send cont nack */
+		cpr_write(cpr_vreg, REG_RBIF_CONT_NACK_CMD, 1);
+
+		/*
+		 * Make sure cpr starts next measurement with
+		 * toggling busy bit
+		 */
+		mb();
+
+		/*
+		 * Wait for controller to finish measurement
+		 * and time-out after 5ms
+		 */
+		retries = 50;
+		while (retries-- && cpr_ctl_is_busy(cpr_vreg))
+			udelay(100);
+
+		if (retries < 0) {
+			cpr_err(cpr_vreg, "Aging calibration failed\n");
+			rc = -EBUSY;
+			goto _exit;
+		}
+
+		/* Check for PAGE_IS_AGE flag in status register */
+		val = cpr_read(cpr_vreg, REG_RBCPR_HTOL_AGE);
+		is_aging_measurement = val & RBCPR_AGE_DATA_STATUS;
+
+		val = cpr_read(cpr_vreg, REG_RBCPR_RESULT_1);
+		sel_fast = RBCPR_RESULT_1_SEL_FAST(val);
+		cpr_debug(cpr_vreg, "RBCPR_RESULT_1 = 0x%08x\n", val);
+
+		val = cpr_read(cpr_vreg, REG_RBCPR_DEBUG1);
+		cpr_debug(cpr_vreg, "RBCPR_DEBUG1 = 0x%08x\n", val);
+
+		if (sel_fast == 1) {
+			quot_min = RBCPR_DEBUG1_QUOT_FAST(val);
+			quot_max = RBCPR_DEBUG1_QUOT_SLOW(val);
+		} else {
+			quot_min = RBCPR_DEBUG1_QUOT_SLOW(val);
+			quot_max = RBCPR_DEBUG1_QUOT_FAST(val);
+		}
+
+		/*
+		 * 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);
+
+		quot_delta_scaled = 0;
+		if (is_aging_measurement) {
+			quot_delta_scaled = quot_min_scaled - quot_max_scaled;
+			quot_delta_results[aging_measurement_count++] =
+					quot_delta_scaled;
+		}
+
+		cpr_debug(cpr_vreg,
+			"Age sensor[%d]: measurement[%d]: page_is_age=%u quot_min = %d, quot_max = %d quot_min_scaled = %d, quot_max_scaled = %d quot_delta_scaled = %d\n",
+			aging_sensor_info->sensor_id, i, is_aging_measurement,
+			quot_min, quot_max, quot_min_scaled, quot_max_scaled,
+			quot_delta_scaled);
+	}
+
+	filtered_count
+		= aging_measurement_count - CPR_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
+					+ CPR_AGING_MEASUREMENT_FILTER];
+
+		aging_sensor_info->current_quot_diff
+			= quot_delta_scaled_sum / filtered_count;
+		cpr_debug(cpr_vreg,
+			"Age sensor[%d]: average aging quotient delta = %d (count = %d)\n",
+			aging_sensor_info->sensor_id,
+			aging_sensor_info->current_quot_diff, filtered_count);
+	} else {
+		cpr_err(cpr_vreg, "%d aging measurements completed after %d iterations\n",
+			aging_measurement_count,
+			CPR_AGING_MEASUREMENT_ITERATIONS);
+		rc = -EBUSY;
+	}
+
+_exit:
+	/* Clear age page bit */
+	cpr_write(cpr_vreg, REG_RBCPR_HTOL_AGE, 0x0);
+
+	/* Disable the CPR controller after aging procedure */
+	cpr_ctl_modify(cpr_vreg, RBCPR_CTL_LOOP_EN, 0x0);
+
+	/* Clear the sensor bypass */
+	if (cpr_vreg->aging_info->aging_sensor_bypass)
+		cpr_write(cpr_vreg, REG_RBCPR_SENSOR_BYPASS0, 0x0);
+
+	/* Unmask all sensors */
+	cpr_write(cpr_vreg, REG_RBCPR_SENSOR_MASK0, 0x0);
+
+	/* Clear gcnt0/1 registers */
+	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(0), 0x0);
+	cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(1), 0x0);
+
+	/* Program the delay count for the timer */
+	val = (cpr_vreg->ref_clk_khz * cpr_vreg->timer_delay_us) / 1000;
+	cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, val);
+
+	kfree(quot_delta_results);
+
+	return rc;
+}
+
+static void cpr_de_aging_adjustment(void *data)
+{
+	struct cpr_regulator *cpr_vreg = (struct cpr_regulator *)data;
+	struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
+	struct cpr_aging_sensor_info *aging_sensor_info;
+	int i, num_aging_sensors, retries, rc = 0;
+	int max_quot_diff = 0, ro_sel = 0;
+	u32 voltage_adjust, aging_voltage_adjust = 0;
+
+	aging_sensor_info = aging_info->sensor_info;
+	num_aging_sensors = aging_info->num_aging_sensors;
+
+	for (i = 0; i < num_aging_sensors; i++, aging_sensor_info++) {
+		retries = 2;
+		while (retries--) {
+			rc = cpr_get_aging_quot_delta(cpr_vreg,
+					aging_sensor_info);
+			if (!rc)
+				break;
+		}
+		if (rc && retries < 0) {
+			cpr_err(cpr_vreg, "error in age calibration: rc = %d\n",
+				rc);
+			aging_info->cpr_aging_error = true;
+			return;
+		}
+
+		max_quot_diff = max(max_quot_diff,
+					(aging_sensor_info->current_quot_diff -
+					aging_sensor_info->initial_quot_diff));
+	}
+
+	cpr_debug(cpr_vreg, "Max aging quot delta = %d\n",
+				max_quot_diff);
+	aging_voltage_adjust = DIV_ROUND_UP(max_quot_diff * 1000000,
+					aging_info->aging_ro_kv);
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		/* Remove initial max aging adjustment */
+		ro_sel = cpr_vreg->cpr_fuse_ro_sel[i];
+		cpr_vreg->cpr_fuse_target_quot[i] -=
+				(aging_info->cpr_ro_kv[ro_sel]
+				* aging_info->max_aging_margin) / 1000000;
+		aging_info->voltage_adjust[i] = 0;
+
+		if (aging_voltage_adjust > 0) {
+			/* Add required aging adjustment */
+			voltage_adjust = (aging_voltage_adjust
+					* aging_info->aging_derate[i]) / 1000;
+			voltage_adjust = min(voltage_adjust,
+						aging_info->max_aging_margin);
+			cpr_vreg->cpr_fuse_target_quot[i] +=
+					(aging_info->cpr_ro_kv[ro_sel]
+					* voltage_adjust) / 1000000;
+			aging_info->voltage_adjust[i] = voltage_adjust;
+		}
+	}
+}
+
+static int cpr_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+
+	return cpr_vreg->vreg_enabled;
+}
+
+static int cpr_regulator_enable(struct regulator_dev *rdev)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	/* Enable dependency power before vdd_apc */
+	if (cpr_vreg->vdd_mx) {
+		rc = regulator_enable(cpr_vreg->vdd_mx);
+		if (rc) {
+			cpr_err(cpr_vreg, "regulator_enable: vdd_mx: rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	rc = regulator_enable(cpr_vreg->vdd_apc);
+	if (rc) {
+		cpr_err(cpr_vreg, "regulator_enable: vdd_apc: rc=%d\n", rc);
+		return rc;
+	}
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+	cpr_vreg->vreg_enabled = true;
+	if (cpr_is_allowed(cpr_vreg) && cpr_vreg->corner) {
+		cpr_irq_clr(cpr_vreg);
+		cpr_corner_restore(cpr_vreg, cpr_vreg->corner);
+		cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
+	}
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	return rc;
+}
+
+static int cpr_regulator_disable(struct regulator_dev *rdev)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = regulator_disable(cpr_vreg->vdd_apc);
+	if (!rc) {
+		if (cpr_vreg->vdd_mx)
+			rc = regulator_disable(cpr_vreg->vdd_mx);
+
+		if (rc) {
+			cpr_err(cpr_vreg, "regulator_disable: vdd_mx: rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		mutex_lock(&cpr_vreg->cpr_mutex);
+		cpr_vreg->vreg_enabled = false;
+		if (cpr_is_allowed(cpr_vreg))
+			cpr_ctl_disable(cpr_vreg);
+		mutex_unlock(&cpr_vreg->cpr_mutex);
+	} else {
+		cpr_err(cpr_vreg, "regulator_disable: vdd_apc: rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+static int cpr_calculate_de_aging_margin(struct cpr_regulator *cpr_vreg)
+{
+	struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
+	enum voltage_change_dir change_dir = NO_CHANGE;
+	u32 save_ctl, save_irq;
+	cpumask_t tmp_mask;
+	int rc = 0, i;
+
+	save_ctl = cpr_read(cpr_vreg, REG_RBCPR_CTL);
+	save_irq = cpr_read(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line));
+
+	/* Disable interrupt and CPR */
+	cpr_irq_set(cpr_vreg, 0);
+	cpr_write(cpr_vreg, REG_RBCPR_CTL, 0);
+
+	if (aging_info->aging_corner > cpr_vreg->corner)
+		change_dir = UP;
+	else if (aging_info->aging_corner < cpr_vreg->corner)
+		change_dir = DOWN;
+
+	/* set selected reference voltage for de-aging */
+	rc = cpr_scale_voltage(cpr_vreg,
+				aging_info->aging_corner,
+				aging_info->aging_ref_voltage,
+				change_dir);
+	if (rc) {
+		cpr_err(cpr_vreg, "Unable to set aging reference voltage, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	/* Force PWM mode */
+	rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_NORMAL);
+	if (rc) {
+		cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n",
+			REGULATOR_MODE_NORMAL, rc);
+		return rc;
+	}
+
+	get_online_cpus();
+	cpumask_and(&tmp_mask, &cpr_vreg->cpu_mask, cpu_online_mask);
+	if (!cpumask_empty(&tmp_mask)) {
+		smp_call_function_any(&tmp_mask,
+					cpr_de_aging_adjustment,
+					cpr_vreg, true);
+		aging_info->cpr_aging_done = true;
+		if (!aging_info->cpr_aging_error)
+			for (i = CPR_FUSE_CORNER_MIN;
+					i <= cpr_vreg->num_fuse_corners; i++)
+				cpr_info(cpr_vreg, "Corner[%d]: age adjusted target quot = %d\n",
+					i, cpr_vreg->cpr_fuse_target_quot[i]);
+	}
+
+	put_online_cpus();
+
+	/* Set to initial mode */
+	rc = regulator_set_mode(cpr_vreg->vdd_apc, REGULATOR_MODE_IDLE);
+	if (rc) {
+		cpr_err(cpr_vreg, "unable to configure vdd-supply for mode=%u, rc=%d\n",
+			REGULATOR_MODE_IDLE, rc);
+		return rc;
+	}
+
+	/* Clear interrupts */
+	cpr_irq_clr(cpr_vreg);
+
+	/* Restore register values */
+	cpr_irq_set(cpr_vreg, save_irq);
+	cpr_write(cpr_vreg, REG_RBCPR_CTL, save_ctl);
+
+	return rc;
+}
+
+/* Note that cpr_vreg->cpr_mutex must be held by the caller. */
+static int cpr_regulator_set_voltage(struct regulator_dev *rdev,
+		int corner, bool reset_quot)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+	struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
+	int rc;
+	int new_volt;
+	enum voltage_change_dir change_dir = NO_CHANGE;
+	int fuse_corner = cpr_vreg->corner_map[corner];
+
+	if (cpr_is_allowed(cpr_vreg)) {
+		cpr_ctl_disable(cpr_vreg);
+		new_volt = cpr_vreg->last_volt[corner];
+	} else {
+		new_volt = cpr_vreg->open_loop_volt[corner];
+	}
+
+	cpr_debug(cpr_vreg, "[corner:%d, fuse_corner:%d] = %d uV\n",
+		corner, fuse_corner, new_volt);
+
+	if (corner > cpr_vreg->corner)
+		change_dir = UP;
+	else if (corner < cpr_vreg->corner)
+		change_dir = DOWN;
+
+	/* Read age sensor data and apply de-aging adjustments */
+	if (cpr_vreg->vreg_enabled && aging_info && !aging_info->cpr_aging_done
+		&& (corner <= aging_info->aging_corner)) {
+		rc = cpr_calculate_de_aging_margin(cpr_vreg);
+		if (rc) {
+			cpr_err(cpr_vreg, "failed in de-aging calibration: rc=%d\n",
+				rc);
+		} else {
+			change_dir = NO_CHANGE;
+			if (corner > aging_info->aging_corner)
+				change_dir = UP;
+			else if (corner  < aging_info->aging_corner)
+				change_dir = DOWN;
+		}
+		reset_quot = true;
+	}
+
+	rc = cpr_scale_voltage(cpr_vreg, corner, new_volt, change_dir);
+	if (rc)
+		return rc;
+
+	if (cpr_is_allowed(cpr_vreg) && cpr_vreg->vreg_enabled) {
+		cpr_irq_clr(cpr_vreg);
+		if (reset_quot)
+			cpr_corner_restore(cpr_vreg, corner);
+		else
+			cpr_corner_switch(cpr_vreg, corner);
+		cpr_ctl_enable(cpr_vreg, corner);
+	}
+
+	cpr_vreg->corner = corner;
+
+	return rc;
+}
+
+static int cpr_regulator_set_voltage_op(struct regulator_dev *rdev,
+		int corner, int corner_max, unsigned *selector)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+	rc = cpr_regulator_set_voltage(rdev, corner, false);
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	return rc;
+}
+
+static int cpr_regulator_get_voltage(struct regulator_dev *rdev)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+
+	return cpr_vreg->corner;
+}
+
+/**
+ * cpr_regulator_list_corner_voltage() - return the ceiling voltage mapped to
+ *			the specified voltage corner
+ * @rdev:		Regulator device pointer for the cpr-regulator
+ * @corner:		Voltage corner
+ *
+ * This function is passed as a callback function into the regulator ops that
+ * are registered for each cpr-regulator device.
+ *
+ * Return: voltage value in microvolts or -EINVAL if the corner is out of range
+ */
+static int cpr_regulator_list_corner_voltage(struct regulator_dev *rdev,
+		int corner)
+{
+	struct cpr_regulator *cpr_vreg = rdev_get_drvdata(rdev);
+
+	if (corner >= CPR_CORNER_MIN && corner <= cpr_vreg->num_corners)
+		return cpr_vreg->ceiling_volt[corner];
+	else
+		return -EINVAL;
+}
+
+static struct regulator_ops cpr_corner_ops = {
+	.enable			= cpr_regulator_enable,
+	.disable		= cpr_regulator_disable,
+	.is_enabled		= cpr_regulator_is_enabled,
+	.set_voltage		= cpr_regulator_set_voltage_op,
+	.get_voltage		= cpr_regulator_get_voltage,
+	.list_corner_voltage	= cpr_regulator_list_corner_voltage,
+};
+
+#ifdef CONFIG_PM
+static int cpr_suspend(struct cpr_regulator *cpr_vreg)
+{
+	cpr_debug(cpr_vreg, "suspend\n");
+
+	cpr_ctl_disable(cpr_vreg);
+
+	cpr_irq_clr(cpr_vreg);
+
+	return 0;
+}
+
+static int cpr_resume(struct cpr_regulator *cpr_vreg)
+
+{
+	cpr_debug(cpr_vreg, "resume\n");
+
+	cpr_irq_clr(cpr_vreg);
+
+	cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
+
+	return 0;
+}
+
+static int cpr_regulator_suspend(struct platform_device *pdev,
+				 pm_message_t state)
+{
+	struct cpr_regulator *cpr_vreg = platform_get_drvdata(pdev);
+	int rc = 0;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	if (cpr_is_allowed(cpr_vreg))
+		rc = cpr_suspend(cpr_vreg);
+
+	cpr_vreg->is_cpr_suspended = true;
+
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	return rc;
+}
+
+static int cpr_regulator_resume(struct platform_device *pdev)
+{
+	struct cpr_regulator *cpr_vreg = platform_get_drvdata(pdev);
+	int rc = 0;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	cpr_vreg->is_cpr_suspended = false;
+
+	if (cpr_is_allowed(cpr_vreg))
+		rc = cpr_resume(cpr_vreg);
+
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	return rc;
+}
+#else
+#define cpr_regulator_suspend NULL
+#define cpr_regulator_resume NULL
+#endif
+
+static int cpr_config(struct cpr_regulator *cpr_vreg, struct device *dev)
+{
+	int i;
+	u32 val, gcnt, reg;
+	void __iomem *rbcpr_clk;
+	int size;
+
+	if (cpr_vreg->rbcpr_clk_addr) {
+		/* Use 19.2 MHz clock for CPR. */
+		rbcpr_clk = ioremap(cpr_vreg->rbcpr_clk_addr, 4);
+		if (!rbcpr_clk) {
+			cpr_err(cpr_vreg, "Unable to map rbcpr_clk\n");
+			return -EINVAL;
+		}
+		reg = readl_relaxed(rbcpr_clk);
+		reg &= ~RBCPR_CLK_SEL_MASK;
+		reg |= RBCPR_CLK_SEL_19P2_MHZ & RBCPR_CLK_SEL_MASK;
+		writel_relaxed(reg, rbcpr_clk);
+		iounmap(rbcpr_clk);
+	}
+
+	/* Disable interrupt and CPR */
+	cpr_write(cpr_vreg, REG_RBIF_IRQ_EN(cpr_vreg->irq_line), 0);
+	cpr_write(cpr_vreg, REG_RBCPR_CTL, 0);
+
+	/* Program the default HW Ceiling, Floor and vlevel */
+	val = ((RBIF_LIMIT_CEILING_DEFAULT & RBIF_LIMIT_CEILING_MASK)
+			<< RBIF_LIMIT_CEILING_SHIFT)
+		| (RBIF_LIMIT_FLOOR_DEFAULT & RBIF_LIMIT_FLOOR_MASK);
+	cpr_write(cpr_vreg, REG_RBIF_LIMIT, val);
+	cpr_write(cpr_vreg, REG_RBIF_SW_VLEVEL, RBIF_SW_VLEVEL_DEFAULT);
+
+	/* Clear the target quotient value and gate count of all ROs */
+	for (i = 0; i < CPR_NUM_RING_OSC; i++)
+		cpr_write(cpr_vreg, REG_RBCPR_GCNT_TARGET(i), 0);
+
+	/* Init and save gcnt */
+	gcnt = (cpr_vreg->ref_clk_khz * cpr_vreg->gcnt_time_us) / 1000;
+	gcnt = (gcnt & RBCPR_GCNT_TARGET_GCNT_MASK) <<
+			RBCPR_GCNT_TARGET_GCNT_SHIFT;
+	cpr_vreg->gcnt = gcnt;
+
+	/* Program the delay count for the timer */
+	val = (cpr_vreg->ref_clk_khz * cpr_vreg->timer_delay_us) / 1000;
+	cpr_write(cpr_vreg, REG_RBCPR_TIMER_INTERVAL, val);
+	cpr_info(cpr_vreg, "Timer count: 0x%0x (for %d us)\n", val,
+		cpr_vreg->timer_delay_us);
+
+	/* Program Consecutive Up & Down */
+	val = ((cpr_vreg->timer_cons_down & RBIF_TIMER_ADJ_CONS_DOWN_MASK)
+			<< RBIF_TIMER_ADJ_CONS_DOWN_SHIFT) |
+	       (cpr_vreg->timer_cons_up & RBIF_TIMER_ADJ_CONS_UP_MASK) |
+	       ((cpr_vreg->clamp_timer_interval & RBIF_TIMER_ADJ_CLAMP_INT_MASK)
+			<< RBIF_TIMER_ADJ_CLAMP_INT_SHIFT);
+	cpr_write(cpr_vreg, REG_RBIF_TIMER_ADJUST, val);
+
+	/* Program the control register */
+	cpr_vreg->up_threshold &= RBCPR_CTL_UP_THRESHOLD_MASK;
+	cpr_vreg->down_threshold &= RBCPR_CTL_DN_THRESHOLD_MASK;
+	val = (cpr_vreg->up_threshold << RBCPR_CTL_UP_THRESHOLD_SHIFT)
+		| (cpr_vreg->down_threshold << RBCPR_CTL_DN_THRESHOLD_SHIFT);
+	val |= RBCPR_CTL_TIMER_EN | RBCPR_CTL_COUNT_MODE;
+	val |= RBCPR_CTL_SW_AUTO_CONT_ACK_EN;
+	cpr_write(cpr_vreg, REG_RBCPR_CTL, val);
+
+	cpr_irq_set(cpr_vreg, CPR_INT_DEFAULT);
+
+	val = cpr_read(cpr_vreg, REG_RBCPR_VERSION);
+	if (val <= RBCPR_VER_2)
+		cpr_vreg->flags |= FLAGS_IGNORE_1ST_IRQ_STATUS;
+
+	size = cpr_vreg->num_corners + 1;
+	cpr_vreg->save_ctl = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL);
+	cpr_vreg->save_irq = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL);
+	if (!cpr_vreg->save_ctl || !cpr_vreg->save_irq)
+		return -ENOMEM;
+
+	for (i = 1; i < size; i++)
+		cpr_corner_save(cpr_vreg, i);
+
+	return 0;
+}
+
+static int cpr_fuse_is_setting_expected(struct cpr_regulator *cpr_vreg,
+					u32 sel_array[5])
+{
+	u64 fuse_bits;
+	u32 ret;
+
+	fuse_bits = cpr_read_efuse_row(cpr_vreg, sel_array[0], sel_array[4]);
+	ret = (fuse_bits >> sel_array[1]) & ((1 << sel_array[2]) - 1);
+	if (ret == sel_array[3])
+		ret = 1;
+	else
+		ret = 0;
+
+	cpr_info(cpr_vreg, "[row:%d] = 0x%llx @%d:%d == %d ?: %s\n",
+			sel_array[0], fuse_bits,
+			sel_array[1], sel_array[2],
+			sel_array[3],
+			(ret == 1) ? "yes" : "no");
+	return ret;
+}
+
+static int cpr_voltage_uplift_wa_inc_volt(struct cpr_regulator *cpr_vreg,
+					struct device_node *of_node)
+{
+	u32 uplift_voltage;
+	u32 uplift_max_volt = 0;
+	int highest_fuse_corner = cpr_vreg->num_fuse_corners;
+	int rc;
+
+	rc = of_property_read_u32(of_node,
+		"qcom,cpr-uplift-voltage", &uplift_voltage);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "cpr-uplift-voltage is missing, rc = %d", rc);
+		return rc;
+	}
+	rc = of_property_read_u32(of_node,
+		"qcom,cpr-uplift-max-volt", &uplift_max_volt);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "cpr-uplift-max-volt is missing, rc = %d",
+			rc);
+		return rc;
+	}
+
+	cpr_vreg->pvs_corner_v[highest_fuse_corner] += uplift_voltage;
+	if (cpr_vreg->pvs_corner_v[highest_fuse_corner] > uplift_max_volt)
+		cpr_vreg->pvs_corner_v[highest_fuse_corner] = uplift_max_volt;
+
+	return rc;
+}
+
+static int cpr_adjust_init_voltages(struct device_node *of_node,
+				struct cpr_regulator *cpr_vreg)
+{
+	int tuple_count, tuple_match, i;
+	u32 index;
+	u32 volt_adjust = 0;
+	int len = 0;
+	int rc = 0;
+
+	if (!of_find_property(of_node, "qcom,cpr-init-voltage-adjustment",
+				&len)) {
+		/* No initial voltage adjustment needed. */
+		return 0;
+	}
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/*
+			 * No matching index to use for initial voltage
+			 * adjustment.
+			 */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "qcom,cpr-init-voltage-adjustment length=%d is invalid\n",
+			len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		index = tuple_match * cpr_vreg->num_fuse_corners
+				+ i - CPR_FUSE_CORNER_MIN;
+		rc = of_property_read_u32_index(of_node,
+			"qcom,cpr-init-voltage-adjustment", index,
+			&volt_adjust);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read qcom,cpr-init-voltage-adjustment index %u, rc=%d\n",
+				index, rc);
+			return rc;
+		}
+
+		if (volt_adjust) {
+			cpr_vreg->pvs_corner_v[i] += volt_adjust;
+			cpr_info(cpr_vreg, "adjusted initial voltage[%d]: %d -> %d uV\n",
+				i, cpr_vreg->pvs_corner_v[i] - volt_adjust,
+				cpr_vreg->pvs_corner_v[i]);
+		}
+	}
+
+	return rc;
+}
+
+/*
+ * Property qcom,cpr-fuse-init-voltage specifies the fuse position of the
+ * initial voltage for each fuse corner. MSB of the fuse value is a sign
+ * bit, and the remaining bits define the steps of the offset. Each step has
+ * units of microvolts defined in the qcom,cpr-fuse-init-voltage-step property.
+ * The initial voltages can be calculated using the formula:
+ * pvs_corner_v[corner] = ceiling_volt[corner] + (sign * steps * step_size_uv)
+ */
+static int cpr_pvs_per_corner_init(struct device_node *of_node,
+				struct cpr_regulator *cpr_vreg)
+{
+	u64 efuse_bits;
+	int i, size, sign, steps, step_size_uv, rc;
+	u32 *fuse_sel, *tmp, *ref_uv;
+	struct property *prop;
+	char *init_volt_str;
+
+	init_volt_str = cpr_vreg->cpr_fuse_redundant
+			? "qcom,cpr-fuse-redun-init-voltage"
+			: "qcom,cpr-fuse-init-voltage";
+
+	prop = of_find_property(of_node, init_volt_str, NULL);
+	if (!prop) {
+		cpr_err(cpr_vreg, "%s is missing\n", init_volt_str);
+		return -EINVAL;
+	}
+	size = prop->length / sizeof(u32);
+	if (size != cpr_vreg->num_fuse_corners * 4) {
+		cpr_err(cpr_vreg,
+			"fuse position for init voltages is invalid\n");
+		return -EINVAL;
+	}
+	fuse_sel = kzalloc(sizeof(u32) * size, GFP_KERNEL);
+	if (!fuse_sel) {
+		cpr_err(cpr_vreg, "memory alloc failed.\n");
+		return -ENOMEM;
+	}
+	rc = of_property_read_u32_array(of_node, init_volt_str,
+							fuse_sel, size);
+	if (rc < 0) {
+		cpr_err(cpr_vreg,
+			"read cpr-fuse-init-voltage failed, rc = %d\n", rc);
+		kfree(fuse_sel);
+		return rc;
+	}
+	rc = of_property_read_u32(of_node, "qcom,cpr-init-voltage-step",
+							&step_size_uv);
+	if (rc < 0) {
+		cpr_err(cpr_vreg,
+			"read cpr-init-voltage-step failed, rc = %d\n", rc);
+		kfree(fuse_sel);
+		return rc;
+	}
+
+	ref_uv = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*ref_uv),
+			GFP_KERNEL);
+	if (!ref_uv) {
+		cpr_err(cpr_vreg,
+			"Could not allocate memory for reference voltages\n");
+		kfree(fuse_sel);
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-init-voltage-ref",
+		&ref_uv[CPR_FUSE_CORNER_MIN], cpr_vreg->num_fuse_corners);
+	if (rc < 0) {
+		cpr_err(cpr_vreg,
+			"read qcom,cpr-init-voltage-ref failed, rc = %d\n", rc);
+		kfree(fuse_sel);
+		kfree(ref_uv);
+		return rc;
+	}
+
+	tmp = fuse_sel;
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		efuse_bits = cpr_read_efuse_param(cpr_vreg, fuse_sel[0],
+					fuse_sel[1], fuse_sel[2], fuse_sel[3]);
+		sign = (efuse_bits & (1 << (fuse_sel[2] - 1))) ? -1 : 1;
+		steps = efuse_bits & ((1 << (fuse_sel[2] - 1)) - 1);
+		cpr_vreg->pvs_corner_v[i] =
+				ref_uv[i] + sign * steps * step_size_uv;
+		cpr_vreg->pvs_corner_v[i] = DIV_ROUND_UP(
+				cpr_vreg->pvs_corner_v[i],
+				cpr_vreg->step_volt) *
+				cpr_vreg->step_volt;
+		cpr_debug(cpr_vreg, "corner %d: sign = %d, steps = %d, volt = %d uV\n",
+			i, sign, steps, cpr_vreg->pvs_corner_v[i]);
+		fuse_sel += 4;
+	}
+
+	rc = cpr_adjust_init_voltages(of_node, cpr_vreg);
+	if (rc)
+		goto done;
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		if (cpr_vreg->pvs_corner_v[i]
+		    > cpr_vreg->fuse_ceiling_volt[i]) {
+			cpr_info(cpr_vreg, "Warning: initial voltage[%d] %d above ceiling %d\n",
+				i, cpr_vreg->pvs_corner_v[i],
+				cpr_vreg->fuse_ceiling_volt[i]);
+			cpr_vreg->pvs_corner_v[i]
+				= cpr_vreg->fuse_ceiling_volt[i];
+		} else if (cpr_vreg->pvs_corner_v[i] <
+				cpr_vreg->fuse_floor_volt[i]) {
+			cpr_info(cpr_vreg, "Warning: initial voltage[%d] %d below floor %d\n",
+				i, cpr_vreg->pvs_corner_v[i],
+				cpr_vreg->fuse_floor_volt[i]);
+			cpr_vreg->pvs_corner_v[i]
+				= cpr_vreg->fuse_floor_volt[i];
+		}
+	}
+
+done:
+	kfree(tmp);
+	kfree(ref_uv);
+
+	return rc;
+}
+
+/*
+ * A single PVS bin is stored in a fuse that's position is defined either
+ * in the qcom,pvs-fuse-redun property or in the qcom,pvs-fuse property.
+ * The fuse value defined in the qcom,pvs-fuse-redun-sel property is used
+ * to pick between the primary or redudant PVS fuse position.
+ * After the PVS bin value is read out successfully, it is used as the row
+ * index to get initial voltages for each fuse corner from the voltage table
+ * defined in the qcom,pvs-voltage-table property.
+ */
+static int cpr_pvs_single_bin_init(struct device_node *of_node,
+				struct cpr_regulator *cpr_vreg)
+{
+	u64 efuse_bits;
+	u32 pvs_fuse[4], pvs_fuse_redun_sel[5];
+	int rc, i, stripe_size;
+	bool redundant;
+	size_t pvs_bins;
+	u32 *tmp;
+
+	rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse-redun-sel",
+						pvs_fuse_redun_sel, 5);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "pvs-fuse-redun-sel missing: rc=%d\n", rc);
+		return rc;
+	}
+
+	redundant = cpr_fuse_is_setting_expected(cpr_vreg, pvs_fuse_redun_sel);
+	if (redundant) {
+		rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse-redun",
+								pvs_fuse, 4);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "pvs-fuse-redun missing: rc=%d\n",
+				rc);
+			return rc;
+		}
+	} else {
+		rc = of_property_read_u32_array(of_node, "qcom,pvs-fuse",
+							pvs_fuse, 4);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "pvs-fuse missing: rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* Construct PVS process # from the efuse bits */
+	efuse_bits = cpr_read_efuse_row(cpr_vreg, pvs_fuse[0], pvs_fuse[3]);
+	cpr_vreg->pvs_bin = (efuse_bits >> pvs_fuse[1]) &
+				((1 << pvs_fuse[2]) - 1);
+	pvs_bins = 1 << pvs_fuse[2];
+	stripe_size = cpr_vreg->num_fuse_corners;
+	tmp = kzalloc(sizeof(u32) * pvs_bins * stripe_size, GFP_KERNEL);
+	if (!tmp) {
+		cpr_err(cpr_vreg, "memory alloc failed\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,pvs-voltage-table",
+						tmp, pvs_bins * stripe_size);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "pvs-voltage-table missing: rc=%d\n", rc);
+		kfree(tmp);
+		return rc;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
+		cpr_vreg->pvs_corner_v[i] = tmp[cpr_vreg->pvs_bin *
+						stripe_size + i - 1];
+	kfree(tmp);
+
+	rc = cpr_adjust_init_voltages(of_node, cpr_vreg);
+	if (rc)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * The function reads VDD_MX dependency parameters from device node.
+ * Select the qcom,vdd-mx-corner-map length equal to either num_fuse_corners
+ * or num_corners based on selected vdd-mx-vmin-method.
+ */
+static int cpr_parse_vdd_mx_parameters(struct platform_device *pdev,
+					struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	u32 corner_map_len;
+	int rc, len, size;
+
+	rc = of_property_read_u32(of_node, "qcom,vdd-mx-vmax",
+				&cpr_vreg->vdd_mx_vmax);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "vdd-mx-vmax missing: rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(of_node, "qcom,vdd-mx-vmin-method",
+			 &cpr_vreg->vdd_mx_vmin_method);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "vdd-mx-vmin-method missing: rc=%d\n",
+			rc);
+		return rc;
+	}
+	if (cpr_vreg->vdd_mx_vmin_method > VDD_MX_VMIN_APC_CORNER_MAP) {
+		cpr_err(cpr_vreg, "Invalid vdd-mx-vmin-method(%d)\n",
+			cpr_vreg->vdd_mx_vmin_method);
+		return -EINVAL;
+	}
+
+	switch (cpr_vreg->vdd_mx_vmin_method) {
+	case VDD_MX_VMIN_APC_FUSE_CORNER_MAP:
+		corner_map_len = cpr_vreg->num_fuse_corners;
+		break;
+	case VDD_MX_VMIN_APC_CORNER_MAP:
+		corner_map_len = cpr_vreg->num_corners;
+		break;
+	default:
+		cpr_vreg->vdd_mx_corner_map = NULL;
+		return 0;
+	}
+
+	if (!of_find_property(of_node, "qcom,vdd-mx-corner-map", &len)) {
+		cpr_err(cpr_vreg, "qcom,vdd-mx-corner-map missing");
+		return -EINVAL;
+	}
+
+	size = len / sizeof(u32);
+	if (size != corner_map_len) {
+		cpr_err(cpr_vreg,
+			"qcom,vdd-mx-corner-map length=%d is invalid: required:%u\n",
+			size, corner_map_len);
+		return -EINVAL;
+	}
+
+	cpr_vreg->vdd_mx_corner_map = devm_kzalloc(&pdev->dev,
+		(corner_map_len + 1) * sizeof(*cpr_vreg->vdd_mx_corner_map),
+			GFP_KERNEL);
+	if (!cpr_vreg->vdd_mx_corner_map) {
+		cpr_err(cpr_vreg,
+			"Can't allocate memory for cpr_vreg->vdd_mx_corner_map\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node,
+				"qcom,vdd-mx-corner-map",
+				&cpr_vreg->vdd_mx_corner_map[1],
+				corner_map_len);
+	if (rc)
+		cpr_err(cpr_vreg,
+			"read qcom,vdd-mx-corner-map failed, rc = %d\n", rc);
+
+	return rc;
+}
+
+#define MAX_CHARS_PER_INT	10
+
+/*
+ * The initial voltage for each fuse corner may be determined by one of two
+ * possible styles of fuse. If qcom,cpr-fuse-init-voltage is present, then
+ * the initial voltages are encoded in a fuse for each fuse corner. If it is
+ * not present, then the initial voltages are all determined using a single
+ * PVS bin fuse value.
+ */
+static int cpr_pvs_init(struct platform_device *pdev,
+			       struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int highest_fuse_corner = cpr_vreg->num_fuse_corners;
+	int i, rc, pos;
+	size_t buflen;
+	char *buf;
+
+	rc = of_property_read_u32(of_node, "qcom,cpr-apc-volt-step",
+					&cpr_vreg->step_volt);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "read cpr-apc-volt-step failed, rc = %d\n",
+			rc);
+		return rc;
+	} else if (cpr_vreg->step_volt == 0) {
+		cpr_err(cpr_vreg, "apc voltage step size can't be set to 0.\n");
+		return -EINVAL;
+	}
+
+	if (of_find_property(of_node, "qcom,cpr-fuse-init-voltage", NULL)) {
+		rc = cpr_pvs_per_corner_init(of_node, cpr_vreg);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "get pvs per corner failed, rc = %d",
+				rc);
+			return rc;
+		}
+	} else {
+		rc = cpr_pvs_single_bin_init(of_node, cpr_vreg);
+		if (rc < 0) {
+			cpr_err(cpr_vreg,
+				"get pvs from single bin failed, rc = %d", rc);
+			return rc;
+		}
+	}
+
+	if (cpr_vreg->flags & FLAGS_UPLIFT_QUOT_VOLT) {
+		rc = cpr_voltage_uplift_wa_inc_volt(cpr_vreg, of_node);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "pvs volt uplift wa apply failed: %d",
+				rc);
+			return rc;
+		}
+	}
+
+	/*
+	 * Allow the highest fuse corner's PVS voltage to define the ceiling
+	 * voltage for that corner in order to support SoC's in which variable
+	 * ceiling values are required.
+	 */
+	if (cpr_vreg->pvs_corner_v[highest_fuse_corner] >
+		cpr_vreg->fuse_ceiling_volt[highest_fuse_corner])
+		cpr_vreg->fuse_ceiling_volt[highest_fuse_corner] =
+			cpr_vreg->pvs_corner_v[highest_fuse_corner];
+
+	/*
+	 * Restrict all fuse corner PVS voltages based upon per corner
+	 * ceiling and floor voltages.
+	 */
+	for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++)
+		if (cpr_vreg->pvs_corner_v[i] > cpr_vreg->fuse_ceiling_volt[i])
+			cpr_vreg->pvs_corner_v[i]
+				= cpr_vreg->fuse_ceiling_volt[i];
+		else if (cpr_vreg->pvs_corner_v[i]
+				< cpr_vreg->fuse_floor_volt[i])
+			cpr_vreg->pvs_corner_v[i]
+				= cpr_vreg->fuse_floor_volt[i];
+
+	cpr_vreg->ceiling_max
+		= cpr_vreg->fuse_ceiling_volt[highest_fuse_corner];
+
+	/*
+	 * Log ceiling, floor, and inital voltages since they are critical for
+	 * all CPR debugging.
+	 */
+	buflen = cpr_vreg->num_fuse_corners * (MAX_CHARS_PER_INT + 2)
+			* sizeof(*buf);
+	buf = kzalloc(buflen, GFP_KERNEL);
+	if (buf == NULL) {
+		cpr_err(cpr_vreg, "Could not allocate memory for corner voltage logging\n");
+		return 0;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++)
+		pos += scnprintf(buf + pos, buflen - pos, "%u%s",
+				cpr_vreg->pvs_corner_v[i],
+				i < highest_fuse_corner ? " " : "");
+	cpr_info(cpr_vreg, "pvs voltage: [%s] uV\n", buf);
+
+	for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++)
+		pos += scnprintf(buf + pos, buflen - pos, "%d%s",
+				cpr_vreg->fuse_ceiling_volt[i],
+				i < highest_fuse_corner ? " " : "");
+	cpr_info(cpr_vreg, "ceiling voltage: [%s] uV\n", buf);
+
+	for (i = CPR_FUSE_CORNER_MIN, pos = 0; i <= highest_fuse_corner; i++)
+		pos += scnprintf(buf + pos, buflen - pos, "%d%s",
+				cpr_vreg->fuse_floor_volt[i],
+				i < highest_fuse_corner ? " " : "");
+	cpr_info(cpr_vreg, "floor voltage: [%s] uV\n", buf);
+
+	kfree(buf);
+	return 0;
+}
+
+#define CPR_PROP_READ_U32(cpr_vreg, of_node, cpr_property, cpr_config, rc) \
+do {									\
+	if (!rc) {							\
+		rc = of_property_read_u32(of_node,			\
+				"qcom," cpr_property,			\
+				cpr_config);				\
+		if (rc) {						\
+			cpr_err(cpr_vreg, "Missing " #cpr_property	\
+				": rc = %d\n", rc);			\
+		}							\
+	}								\
+} while (0)
+
+static int cpr_apc_init(struct platform_device *pdev,
+			       struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int i, rc = 0;
+
+	for (i = 0; i < ARRAY_SIZE(vdd_apc_name); i++) {
+		cpr_vreg->vdd_apc = devm_regulator_get_optional(&pdev->dev,
+					vdd_apc_name[i]);
+		rc = PTR_RET(cpr_vreg->vdd_apc);
+		if (!IS_ERR_OR_NULL(cpr_vreg->vdd_apc))
+			break;
+	}
+
+	if (rc) {
+		if (rc != -EPROBE_DEFER)
+			cpr_err(cpr_vreg, "devm_regulator_get: rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Check dependencies */
+	if (of_find_property(of_node, "vdd-mx-supply", NULL)) {
+		cpr_vreg->vdd_mx = devm_regulator_get(&pdev->dev, "vdd-mx");
+		if (IS_ERR_OR_NULL(cpr_vreg->vdd_mx)) {
+			rc = PTR_RET(cpr_vreg->vdd_mx);
+			if (rc != -EPROBE_DEFER)
+				cpr_err(cpr_vreg,
+					"devm_regulator_get: vdd-mx: rc=%d\n",
+					rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static void cpr_apc_exit(struct cpr_regulator *cpr_vreg)
+{
+	if (cpr_vreg->vreg_enabled) {
+		regulator_disable(cpr_vreg->vdd_apc);
+
+		if (cpr_vreg->vdd_mx)
+			regulator_disable(cpr_vreg->vdd_mx);
+	}
+}
+
+static int cpr_voltage_uplift_wa_inc_quot(struct cpr_regulator *cpr_vreg,
+					struct device_node *of_node)
+{
+	u32 delta_quot[3];
+	int rc, i;
+
+	rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-uplift-quotient", delta_quot, 3);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "cpr-uplift-quotient is missing: %d", rc);
+		return rc;
+	}
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
+		cpr_vreg->cpr_fuse_target_quot[i] += delta_quot[i-1];
+	return rc;
+}
+
+static void cpr_parse_pvs_version_fuse(struct cpr_regulator *cpr_vreg,
+				struct device_node *of_node)
+{
+	int rc;
+	u64 fuse_bits;
+	u32 fuse_sel[4];
+
+	rc = of_property_read_u32_array(of_node,
+			"qcom,pvs-version-fuse-sel", fuse_sel, 4);
+	if (!rc) {
+		fuse_bits = cpr_read_efuse_row(cpr_vreg,
+				fuse_sel[0], fuse_sel[3]);
+		cpr_vreg->pvs_version = (fuse_bits >> fuse_sel[1]) &
+			((1 << fuse_sel[2]) - 1);
+		cpr_info(cpr_vreg, "[row: %d]: 0x%llx, pvs_version = %d\n",
+				fuse_sel[0], fuse_bits, cpr_vreg->pvs_version);
+	} else {
+		cpr_vreg->pvs_version = 0;
+	}
+}
+
+/**
+ * cpr_get_open_loop_voltage() - fill the open_loop_volt array with linearly
+ *				 interpolated open-loop CPR voltage values.
+ * @cpr_vreg:	Handle to the cpr-regulator device
+ * @dev:	Device pointer for the cpr-regulator device
+ * @corner_max:	Array of length (cpr_vreg->num_fuse_corners + 1) which maps from
+ *		fuse corners to the highest virtual corner corresponding to a
+ *		given fuse corner
+ * @freq_map:	Array of length (cpr_vreg->num_corners + 1) which maps from
+ *		virtual corners to frequencies in Hz.
+ * @maps_valid:	Boolean which indicates if the values in corner_max and freq_map
+ *		are valid.  If they are not valid, then the open_loop_volt
+ *		values are not interpolated.
+ */
+static int cpr_get_open_loop_voltage(struct cpr_regulator *cpr_vreg,
+		struct device *dev, const u32 *corner_max, const u32 *freq_map,
+		bool maps_valid)
+{
+	int rc = 0;
+	int i, j;
+	u64 volt_high, volt_low, freq_high, freq_low, freq, temp, temp_limit;
+	u32 *max_factor = NULL;
+
+	cpr_vreg->open_loop_volt = devm_kzalloc(dev,
+			sizeof(int) * (cpr_vreg->num_corners + 1), GFP_KERNEL);
+	if (!cpr_vreg->open_loop_volt) {
+		cpr_err(cpr_vreg,
+			"Can't allocate memory for cpr_vreg->open_loop_volt\n");
+		return -ENOMEM;
+	}
+
+	/*
+	 * Set open loop voltage to be equal to per-fuse-corner initial voltage
+	 * by default.  This ensures that the open loop voltage is valid for
+	 * all virtual corners even if some virtual corner to frequency mappings
+	 * are missing.  It also ensures that the voltage is valid for the
+	 * higher corners not utilized by a given speed-bin.
+	 */
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
+		cpr_vreg->open_loop_volt[i]
+			= cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]];
+
+	if (!maps_valid || !corner_max || !freq_map
+	    || !of_find_property(dev->of_node,
+				 "qcom,cpr-voltage-scaling-factor-max", NULL)) {
+		/* Not using interpolation */
+		return 0;
+	}
+
+	max_factor
+	       = kzalloc(sizeof(*max_factor) * (cpr_vreg->num_fuse_corners + 1),
+			 GFP_KERNEL);
+	if (!max_factor) {
+		cpr_err(cpr_vreg, "Could not allocate memory for max_factor array\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(dev->of_node,
+			"qcom,cpr-voltage-scaling-factor-max",
+			&max_factor[CPR_FUSE_CORNER_MIN],
+			cpr_vreg->num_fuse_corners);
+	if (rc) {
+		cpr_debug(cpr_vreg, "failed to read qcom,cpr-voltage-scaling-factor-max; initial voltage interpolation not possible\n");
+		kfree(max_factor);
+		return 0;
+	}
+
+	for (j = CPR_FUSE_CORNER_MIN + 1; j <= cpr_vreg->num_fuse_corners;
+	    j++) {
+		freq_high = freq_map[corner_max[j]];
+		freq_low = freq_map[corner_max[j - 1]];
+		volt_high = cpr_vreg->pvs_corner_v[j];
+		volt_low = cpr_vreg->pvs_corner_v[j - 1];
+		if (freq_high <= freq_low || volt_high <= volt_low)
+			continue;
+
+		for (i = corner_max[j - 1] + 1; i < corner_max[j]; i++) {
+			freq = freq_map[i];
+			if (freq_high <= freq)
+				continue;
+
+			temp = (freq_high - freq) * (volt_high - volt_low);
+			do_div(temp, (u32)(freq_high - freq_low));
+
+			/*
+			 * max_factor[j] has units of uV/MHz while freq values
+			 * have units of Hz.  Divide by 1000000 to convert.
+			 */
+			temp_limit = (freq_high - freq) * max_factor[j];
+			do_div(temp_limit, 1000000);
+
+			cpr_vreg->open_loop_volt[i]
+				= volt_high - min(temp, temp_limit);
+			cpr_vreg->open_loop_volt[i]
+				= DIV_ROUND_UP(cpr_vreg->open_loop_volt[i],
+						cpr_vreg->step_volt)
+					* cpr_vreg->step_volt;
+		}
+	}
+
+	kfree(max_factor);
+	return 0;
+}
+
+/*
+ * Limit the per-virtual-corner open-loop voltages using the per-virtual-corner
+ * ceiling and floor voltage values.  This must be called only after the
+ * open_loop_volt, ceiling, and floor arrays have all been initialized.
+ */
+static int cpr_limit_open_loop_voltage(struct cpr_regulator *cpr_vreg)
+{
+	int i;
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		if (cpr_vreg->open_loop_volt[i] > cpr_vreg->ceiling_volt[i])
+			cpr_vreg->open_loop_volt[i] = cpr_vreg->ceiling_volt[i];
+		else if (cpr_vreg->open_loop_volt[i] < cpr_vreg->floor_volt[i])
+			cpr_vreg->open_loop_volt[i] = cpr_vreg->floor_volt[i];
+	}
+
+	return 0;
+}
+
+/*
+ * Fill an OPP table for the cpr-regulator device struct with pairs of
+ * <virtual voltage corner number, open loop voltage> tuples.
+ */
+static int cpr_populate_opp_table(struct cpr_regulator *cpr_vreg,
+				struct device *dev)
+{
+	int i, rc = 0;
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		rc |= dev_pm_opp_add(dev, i, cpr_vreg->open_loop_volt[i]);
+		if (rc)
+			cpr_debug(cpr_vreg, "could not add OPP entry <%d, %d>, rc=%d\n",
+				i, cpr_vreg->open_loop_volt[i], rc);
+	}
+	if (rc)
+		cpr_err(cpr_vreg, "adding OPP entry failed - OPP may not be enabled, rc=%d\n",
+				rc);
+
+	return 0;
+}
+
+/*
+ * Conditionally reduce the per-virtual-corner ceiling voltages if certain
+ * device tree flags are present.  This must be called only after the ceiling
+ * array has been initialized and the open_loop_volt array values have been
+ * initialized and limited to the existing floor to ceiling voltage range.
+ */
+static int cpr_reduce_ceiling_voltage(struct cpr_regulator *cpr_vreg,
+				struct device *dev)
+{
+	bool reduce_to_fuse_open_loop, reduce_to_interpolated_open_loop;
+	int i;
+
+	reduce_to_fuse_open_loop = of_property_read_bool(dev->of_node,
+				"qcom,cpr-init-voltage-as-ceiling");
+	reduce_to_interpolated_open_loop = of_property_read_bool(dev->of_node,
+				"qcom,cpr-scaled-init-voltage-as-ceiling");
+
+	if (!reduce_to_fuse_open_loop && !reduce_to_interpolated_open_loop)
+		return 0;
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		if (reduce_to_interpolated_open_loop &&
+		    cpr_vreg->open_loop_volt[i] < cpr_vreg->ceiling_volt[i])
+			cpr_vreg->ceiling_volt[i] = cpr_vreg->open_loop_volt[i];
+		else if (reduce_to_fuse_open_loop &&
+				cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]]
+				< cpr_vreg->ceiling_volt[i])
+			cpr_vreg->ceiling_volt[i]
+				= max((u32)cpr_vreg->floor_volt[i],
+			       cpr_vreg->pvs_corner_v[cpr_vreg->corner_map[i]]);
+		cpr_debug(cpr_vreg, "lowered ceiling[%d] = %d uV\n",
+			i, cpr_vreg->ceiling_volt[i]);
+	}
+
+	return 0;
+}
+
+static int cpr_adjust_target_quot_offsets(struct platform_device *pdev,
+					struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int tuple_count, tuple_match, i;
+	u32 index;
+	u32 quot_offset_adjust = 0;
+	int len = 0;
+	int rc = 0;
+	char *quot_offset_str;
+
+	quot_offset_str = "qcom,cpr-quot-offset-adjustment";
+	if (!of_find_property(of_node, quot_offset_str, &len)) {
+		/* No static quotient adjustment needed. */
+		return 0;
+	}
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/* No matching index to use for quotient adjustment. */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "%s length=%d is invalid\n", quot_offset_str,
+			len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		index = tuple_match * cpr_vreg->num_fuse_corners
+				+ i - CPR_FUSE_CORNER_MIN;
+		rc = of_property_read_u32_index(of_node, quot_offset_str, index,
+			&quot_offset_adjust);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
+				quot_offset_str, index, rc);
+			return rc;
+		}
+
+		if (quot_offset_adjust) {
+			cpr_vreg->fuse_quot_offset[i] += quot_offset_adjust;
+			cpr_info(cpr_vreg, "Corner[%d]: adjusted target quot = %d\n",
+				i, cpr_vreg->fuse_quot_offset[i]);
+		}
+	}
+
+	return rc;
+}
+
+static int cpr_get_fuse_quot_offset(struct cpr_regulator *cpr_vreg,
+					struct platform_device *pdev,
+					struct cpr_quot_scale *quot_scale)
+{
+	struct device *dev = &pdev->dev;
+	struct property *prop;
+	u32 *fuse_sel, *tmp, *offset_multiplier = NULL;
+	int rc = 0, i, size, len;
+	char *quot_offset_str;
+
+	quot_offset_str = cpr_vreg->cpr_fuse_redundant
+			? "qcom,cpr-fuse-redun-quot-offset"
+			: "qcom,cpr-fuse-quot-offset";
+
+	prop = of_find_property(dev->of_node, quot_offset_str, NULL);
+	if (!prop) {
+		cpr_debug(cpr_vreg, "%s not present\n", quot_offset_str);
+		return 0;
+	} else {
+		size = prop->length / sizeof(u32);
+		if (size != cpr_vreg->num_fuse_corners * 4) {
+			cpr_err(cpr_vreg, "fuse position for quot offset is invalid\n");
+			return -EINVAL;
+		}
+	}
+
+	fuse_sel = kzalloc(sizeof(u32) * size, GFP_KERNEL);
+	if (!fuse_sel) {
+		cpr_err(cpr_vreg, "memory alloc failed.\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(dev->of_node, quot_offset_str,
+			fuse_sel, size);
+
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "read %s failed, rc = %d\n", quot_offset_str,
+			rc);
+		kfree(fuse_sel);
+		return rc;
+	}
+
+	cpr_vreg->fuse_quot_offset = devm_kzalloc(dev,
+			sizeof(u32) * (cpr_vreg->num_fuse_corners + 1),
+			GFP_KERNEL);
+	if (!cpr_vreg->fuse_quot_offset) {
+		cpr_err(cpr_vreg, "Can't allocate memory for cpr_vreg->fuse_quot_offset\n");
+		kfree(fuse_sel);
+		return -ENOMEM;
+	}
+
+	if (!of_find_property(dev->of_node,
+				"qcom,cpr-fuse-quot-offset-scale", &len)) {
+		cpr_debug(cpr_vreg, "qcom,cpr-fuse-quot-offset-scale not present\n");
+	} else {
+		if (len != cpr_vreg->num_fuse_corners * sizeof(u32)) {
+			cpr_err(cpr_vreg, "the size of qcom,cpr-fuse-quot-offset-scale is invalid\n");
+			kfree(fuse_sel);
+			return -EINVAL;
+		}
+
+		offset_multiplier = kzalloc(sizeof(*offset_multiplier)
+					* (cpr_vreg->num_fuse_corners + 1),
+					GFP_KERNEL);
+		if (!offset_multiplier) {
+			cpr_err(cpr_vreg, "memory alloc failed.\n");
+			kfree(fuse_sel);
+			return -ENOMEM;
+		}
+
+		rc = of_property_read_u32_array(dev->of_node,
+						"qcom,cpr-fuse-quot-offset-scale",
+						&offset_multiplier[1],
+						cpr_vreg->num_fuse_corners);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "read qcom,cpr-fuse-quot-offset-scale failed, rc = %d\n",
+				rc);
+			kfree(fuse_sel);
+			goto out;
+		}
+	}
+
+	tmp = fuse_sel;
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		cpr_vreg->fuse_quot_offset[i] = cpr_read_efuse_param(cpr_vreg,
+					fuse_sel[0], fuse_sel[1], fuse_sel[2],
+					fuse_sel[3]);
+		if (offset_multiplier)
+			cpr_vreg->fuse_quot_offset[i] *= offset_multiplier[i];
+		fuse_sel += 4;
+	}
+
+	rc = cpr_adjust_target_quot_offsets(pdev, cpr_vreg);
+	kfree(tmp);
+out:
+	kfree(offset_multiplier);
+	return rc;
+}
+
+/*
+ * Adjust the per-virtual-corner open loop voltage with an offset specfied by a
+ * device-tree property. This must be called after open-loop voltage scaling.
+ */
+static int cpr_virtual_corner_voltage_adjust(struct cpr_regulator *cpr_vreg,
+						struct device *dev)
+{
+	char *prop_name = "qcom,cpr-virtual-corner-init-voltage-adjustment";
+	int i, rc, tuple_count, tuple_match, index, len;
+	u32 voltage_adjust;
+
+	if (!of_find_property(dev->of_node, prop_name, &len)) {
+		cpr_debug(cpr_vreg, "%s not specified\n", prop_name);
+		return 0;
+	}
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/* No matching index to use for voltage adjustment. */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_name,
+			len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		index = tuple_match * cpr_vreg->num_corners
+				+ i - CPR_CORNER_MIN;
+		rc = of_property_read_u32_index(dev->of_node, prop_name,
+						index, &voltage_adjust);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
+				prop_name, index, rc);
+			return rc;
+		}
+
+		if (voltage_adjust) {
+			cpr_vreg->open_loop_volt[i] += (int)voltage_adjust;
+			cpr_info(cpr_vreg, "corner=%d adjusted open-loop voltage=%d\n",
+				i, cpr_vreg->open_loop_volt[i]);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Adjust the per-virtual-corner quot with an offset specfied by a
+ * device-tree property. This must be called after the quot-scaling adjustments
+ * are completed.
+ */
+static int cpr_virtual_corner_quot_adjust(struct cpr_regulator *cpr_vreg,
+						struct device *dev)
+{
+	char *prop_name = "qcom,cpr-virtual-corner-quotient-adjustment";
+	int i, rc, tuple_count, tuple_match, index, len;
+	u32 quot_adjust;
+
+	if (!of_find_property(dev->of_node, prop_name, &len)) {
+		cpr_debug(cpr_vreg, "%s not specified\n", prop_name);
+		return 0;
+	}
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/* No matching index to use for quotient adjustment. */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_name,
+			len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		index = tuple_match * cpr_vreg->num_corners
+				+ i - CPR_CORNER_MIN;
+		rc = of_property_read_u32_index(dev->of_node, prop_name,
+						index, &quot_adjust);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
+				prop_name, index, rc);
+			return rc;
+		}
+
+		if (quot_adjust) {
+			cpr_vreg->quot_adjust[i] -= (int)quot_adjust;
+			cpr_info(cpr_vreg, "corner=%d adjusted quotient=%d\n",
+					i,
+			cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]]
+						- cpr_vreg->quot_adjust[i]);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * cpr_get_corner_quot_adjustment() -- get the quot_adjust for each corner.
+ *
+ * Get the virtual corner to fuse corner mapping and virtual corner to APC clock
+ * frequency mapping from device tree.
+ * Calculate the quotient adjustment scaling factor for those corners mapping to
+ * all fuse corners except for the lowest one using linear interpolation.
+ * Calculate the quotient adjustment for each of these virtual corners using the
+ * min of the calculated scaling factor and the constant max scaling factor
+ * defined for each fuse corner in device tree.
+ */
+static int cpr_get_corner_quot_adjustment(struct cpr_regulator *cpr_vreg,
+					struct device *dev)
+{
+	int rc = 0;
+	int highest_fuse_corner = cpr_vreg->num_fuse_corners;
+	int i, j, size;
+	struct property *prop;
+	bool corners_mapped, match_found;
+	u32 *tmp, *freq_map = NULL;
+	u32 corner, freq_corner;
+	u32 *freq_max = NULL;
+	u32 *scaling = NULL;
+	u32 *max_factor = NULL;
+	u32 *corner_max = NULL;
+	bool maps_valid = false;
+
+	prop = of_find_property(dev->of_node, "qcom,cpr-corner-map", NULL);
+
+	if (prop) {
+		size = prop->length / sizeof(u32);
+		corners_mapped = true;
+	} else {
+		size = cpr_vreg->num_fuse_corners;
+		corners_mapped = false;
+	}
+
+	cpr_vreg->corner_map = devm_kzalloc(dev, sizeof(int) * (size + 1),
+					GFP_KERNEL);
+	if (!cpr_vreg->corner_map) {
+		cpr_err(cpr_vreg,
+			"Can't allocate memory for cpr_vreg->corner_map\n");
+		return -ENOMEM;
+	}
+	cpr_vreg->num_corners = size;
+
+	cpr_vreg->quot_adjust = devm_kzalloc(dev,
+			sizeof(u32) * (cpr_vreg->num_corners + 1),
+			GFP_KERNEL);
+	if (!cpr_vreg->quot_adjust) {
+		cpr_err(cpr_vreg,
+			"Can't allocate memory for cpr_vreg->quot_adjust\n");
+		return -ENOMEM;
+	}
+
+	if (!corners_mapped) {
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++)
+			cpr_vreg->corner_map[i] = i;
+		goto free_arrays;
+	} else {
+		rc = of_property_read_u32_array(dev->of_node,
+			"qcom,cpr-corner-map", &cpr_vreg->corner_map[1], size);
+
+		if (rc) {
+			cpr_err(cpr_vreg,
+				"qcom,cpr-corner-map missing, rc = %d\n", rc);
+			return rc;
+		}
+
+		/*
+		 * Verify that the virtual corner to fuse corner mapping is
+		 * valid.
+		 */
+		for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+			if (cpr_vreg->corner_map[i] > cpr_vreg->num_fuse_corners
+			    || cpr_vreg->corner_map[i] < CPR_FUSE_CORNER_MIN) {
+				cpr_err(cpr_vreg, "qcom,cpr-corner-map contains an element %d which isn't in the allowed range [%d, %d]\n",
+					cpr_vreg->corner_map[i],
+					CPR_FUSE_CORNER_MIN,
+					cpr_vreg->num_fuse_corners);
+				return -EINVAL;
+			}
+		}
+	}
+
+	prop = of_find_property(dev->of_node,
+			"qcom,cpr-speed-bin-max-corners", NULL);
+	if (!prop) {
+		cpr_debug(cpr_vreg, "qcom,cpr-speed-bin-max-corner missing\n");
+		goto free_arrays;
+	}
+
+	size = prop->length / sizeof(u32);
+	tmp = kzalloc(size * sizeof(u32), GFP_KERNEL);
+	if (!tmp) {
+		cpr_err(cpr_vreg, "memory alloc failed\n");
+		return -ENOMEM;
+	}
+	rc = of_property_read_u32_array(dev->of_node,
+		"qcom,cpr-speed-bin-max-corners", tmp, size);
+	if (rc < 0) {
+		kfree(tmp);
+		cpr_err(cpr_vreg,
+			"get cpr-speed-bin-max-corners failed, rc = %d\n", rc);
+		return rc;
+	}
+
+	corner_max = kzalloc((cpr_vreg->num_fuse_corners + 1)
+				* sizeof(*corner_max), GFP_KERNEL);
+	freq_max = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*freq_max),
+				GFP_KERNEL);
+	if (corner_max == NULL || freq_max == NULL) {
+		cpr_err(cpr_vreg, "Could not allocate memory for quotient scaling arrays\n");
+		kfree(tmp);
+		rc = -ENOMEM;
+		goto free_arrays;
+	}
+
+	/*
+	 * Get the maximum virtual corner for each fuse corner based upon the
+	 * speed_bin and pvs_version values.
+	 */
+	match_found = false;
+	for (i = 0; i < size; i += cpr_vreg->num_fuse_corners + 2) {
+		if (tmp[i] != cpr_vreg->speed_bin &&
+		    tmp[i] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		if (tmp[i + 1] != cpr_vreg->pvs_version &&
+		    tmp[i + 1] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		for (j = CPR_FUSE_CORNER_MIN;
+		     j <= cpr_vreg->num_fuse_corners; j++)
+			corner_max[j] = tmp[i + 2 + j - CPR_FUSE_CORNER_MIN];
+		match_found = true;
+		break;
+	}
+	kfree(tmp);
+
+	if (!match_found) {
+		cpr_debug(cpr_vreg, "No quotient adjustment possible for speed bin=%u, pvs version=%u\n",
+			cpr_vreg->speed_bin, cpr_vreg->pvs_version);
+		goto free_arrays;
+	}
+
+	/* Verify that fuse corner to max virtual corner mapping is valid. */
+	for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++) {
+		if (corner_max[i] < CPR_CORNER_MIN
+		    || corner_max[i] > cpr_vreg->num_corners) {
+			cpr_err(cpr_vreg, "Invalid corner=%d in qcom,cpr-speed-bin-max-corners\n",
+				corner_max[i]);
+			goto free_arrays;
+		}
+	}
+
+	/*
+	 * Return success if the virtual corner values read from
+	 * qcom,cpr-speed-bin-max-corners property are incorrect.  This allows
+	 * the driver to continue to run without quotient scaling.
+	 */
+	for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) {
+		if (corner_max[i] <= corner_max[i - 1]) {
+			cpr_err(cpr_vreg, "fuse corner=%d (%u) should be larger than the fuse corner=%d (%u)\n",
+				i, corner_max[i], i - 1, corner_max[i - 1]);
+			goto free_arrays;
+		}
+	}
+
+	prop = of_find_property(dev->of_node,
+			"qcom,cpr-corner-frequency-map", NULL);
+	if (!prop) {
+		cpr_debug(cpr_vreg, "qcom,cpr-corner-frequency-map missing\n");
+		goto free_arrays;
+	}
+
+	size = prop->length / sizeof(u32);
+	tmp = kzalloc(sizeof(u32) * size, GFP_KERNEL);
+	if (!tmp) {
+		cpr_err(cpr_vreg, "memory alloc failed\n");
+		rc = -ENOMEM;
+		goto free_arrays;
+	}
+	rc = of_property_read_u32_array(dev->of_node,
+		"qcom,cpr-corner-frequency-map", tmp, size);
+	if (rc < 0) {
+		cpr_err(cpr_vreg,
+			"get cpr-corner-frequency-map failed, rc = %d\n", rc);
+		kfree(tmp);
+		goto free_arrays;
+	}
+	freq_map = kzalloc(sizeof(u32) * (cpr_vreg->num_corners + 1),
+			GFP_KERNEL);
+	if (!freq_map) {
+		cpr_err(cpr_vreg, "memory alloc for freq_map failed!\n");
+		kfree(tmp);
+		rc = -ENOMEM;
+		goto free_arrays;
+	}
+	for (i = 0; i < size; i += 2) {
+		corner = tmp[i];
+		if ((corner < 1) || (corner > cpr_vreg->num_corners)) {
+			cpr_err(cpr_vreg,
+				"corner should be in 1~%d range: %d\n",
+				cpr_vreg->num_corners, corner);
+			continue;
+		}
+		freq_map[corner] = tmp[i + 1];
+		cpr_debug(cpr_vreg,
+				"Frequency at virtual corner %d is %d Hz.\n",
+				corner, freq_map[corner]);
+	}
+	kfree(tmp);
+
+	prop = of_find_property(dev->of_node,
+			"qcom,cpr-quot-adjust-scaling-factor-max", NULL);
+	if (!prop) {
+		cpr_debug(cpr_vreg, "qcom,cpr-quot-adjust-scaling-factor-max missing\n");
+		rc = 0;
+		goto free_arrays;
+	}
+
+	size = prop->length / sizeof(u32);
+	if ((size != 1) && (size != cpr_vreg->num_fuse_corners)) {
+		cpr_err(cpr_vreg, "The size of qcom,cpr-quot-adjust-scaling-factor-max should be 1 or %d\n",
+			cpr_vreg->num_fuse_corners);
+		rc = 0;
+		goto free_arrays;
+	}
+
+	max_factor = kzalloc(sizeof(u32) * (cpr_vreg->num_fuse_corners + 1),
+			GFP_KERNEL);
+	if (!max_factor) {
+		cpr_err(cpr_vreg, "Could not allocate memory for max_factor array\n");
+		rc = -ENOMEM;
+		goto free_arrays;
+	}
+	/*
+	 * Leave max_factor[CPR_FUSE_CORNER_MIN ... highest_fuse_corner-1] = 0
+	 * if cpr-quot-adjust-scaling-factor-max is a single value in order to
+	 * maintain backward compatibility.
+	 */
+	i = (size == cpr_vreg->num_fuse_corners) ? CPR_FUSE_CORNER_MIN
+						 : highest_fuse_corner;
+	rc = of_property_read_u32_array(dev->of_node,
+			"qcom,cpr-quot-adjust-scaling-factor-max",
+			&max_factor[i], size);
+	if (rc < 0) {
+		cpr_debug(cpr_vreg, "could not read qcom,cpr-quot-adjust-scaling-factor-max, rc=%d\n",
+			rc);
+		rc = 0;
+		goto free_arrays;
+	}
+
+	/*
+	 * Get the quotient adjustment scaling factor, according to:
+	 * scaling = min(1000 * (QUOT(corner_N) - QUOT(corner_N-1))
+	 *		/ (freq(corner_N) - freq(corner_N-1)), max_factor)
+	 *
+	 * QUOT(corner_N):	quotient read from fuse for fuse corner N
+	 * QUOT(corner_N-1):	quotient read from fuse for fuse corner (N - 1)
+	 * freq(corner_N):	max frequency in MHz supported by fuse corner N
+	 * freq(corner_N-1):	max frequency in MHz supported by fuse corner
+	 *			 (N - 1)
+	 */
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++)
+		freq_max[i] = freq_map[corner_max[i]];
+	for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) {
+		if (freq_max[i] <= freq_max[i - 1] || freq_max[i - 1] == 0) {
+			cpr_err(cpr_vreg, "fuse corner %d freq=%u should be larger than fuse corner %d freq=%u\n",
+			      i, freq_max[i], i - 1, freq_max[i - 1]);
+			rc = -EINVAL;
+			goto free_arrays;
+		}
+	}
+	scaling = kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*scaling),
+			GFP_KERNEL);
+	if (!scaling) {
+		cpr_err(cpr_vreg, "Could not allocate memory for scaling array\n");
+		rc = -ENOMEM;
+		goto free_arrays;
+	}
+	/* Convert corner max frequencies from Hz to MHz. */
+	for (i = CPR_FUSE_CORNER_MIN; i <= highest_fuse_corner; i++)
+		freq_max[i] /= 1000000;
+
+	for (i = CPR_FUSE_CORNER_MIN + 1; i <= highest_fuse_corner; i++) {
+		if (cpr_vreg->fuse_quot_offset &&
+			(cpr_vreg->cpr_fuse_ro_sel[i] !=
+				cpr_vreg->cpr_fuse_ro_sel[i - 1])) {
+			scaling[i] = 1000 * cpr_vreg->fuse_quot_offset[i]
+				/ (freq_max[i] - freq_max[i - 1]);
+		} else {
+			scaling[i] = 1000 * (cpr_vreg->cpr_fuse_target_quot[i]
+				      - cpr_vreg->cpr_fuse_target_quot[i - 1])
+				  / (freq_max[i] - freq_max[i - 1]);
+			if (cpr_vreg->cpr_fuse_target_quot[i]
+				< cpr_vreg->cpr_fuse_target_quot[i - 1])
+				scaling[i] = 0;
+		}
+		scaling[i] = min(scaling[i], max_factor[i]);
+		cpr_info(cpr_vreg, "fuse corner %d quotient adjustment scaling factor: %d.%03d\n",
+			i, scaling[i] / 1000, scaling[i] % 1000);
+	}
+
+	/*
+	 * Walk through the virtual corners mapped to each fuse corner
+	 * and calculate the quotient adjustment for each one using the
+	 * following formula:
+	 * quot_adjust = (freq_max - freq_corner) * scaling / 1000
+	 *
+	 * @freq_max: max frequency in MHz supported by the fuse corner
+	 * @freq_corner: frequency in MHz corresponding to the virtual corner
+	 */
+	for (j = CPR_FUSE_CORNER_MIN + 1; j <= highest_fuse_corner; j++) {
+		for (i = corner_max[j - 1] + 1; i < corner_max[j]; i++) {
+			freq_corner = freq_map[i] / 1000000; /* MHz */
+			if (freq_corner > 0) {
+				cpr_vreg->quot_adjust[i] = scaling[j] *
+				   (freq_max[j] - freq_corner) / 1000;
+			}
+		}
+	}
+
+	rc = cpr_virtual_corner_quot_adjust(cpr_vreg, dev);
+	if (rc) {
+		cpr_err(cpr_vreg, "count not adjust virtual-corner quot rc=%d\n",
+			rc);
+		goto free_arrays;
+	}
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
+		cpr_info(cpr_vreg, "adjusted quotient[%d] = %d\n", i,
+			cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]]
+			- cpr_vreg->quot_adjust[i]);
+
+	maps_valid = true;
+
+free_arrays:
+	if (!rc) {
+
+		rc = cpr_get_open_loop_voltage(cpr_vreg, dev, corner_max,
+						freq_map, maps_valid);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not fill open loop voltage array, rc=%d\n",
+				rc);
+			goto free_arrays_1;
+		}
+
+		rc = cpr_virtual_corner_voltage_adjust(cpr_vreg, dev);
+		if (rc)
+			cpr_err(cpr_vreg, "count not adjust virtual-corner voltage rc=%d\n",
+				rc);
+	}
+
+free_arrays_1:
+	kfree(max_factor);
+	kfree(scaling);
+	kfree(freq_map);
+	kfree(corner_max);
+	kfree(freq_max);
+	return rc;
+}
+
+/*
+ * Check if the redundant set of CPR fuses should be used in place of the
+ * primary set and configure the cpr_fuse_redundant element accordingly.
+ */
+static int cpr_check_redundant(struct platform_device *pdev,
+		     struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	u32 cpr_fuse_redun_sel[5];
+	int rc;
+
+	if (of_find_property(of_node, "qcom,cpr-fuse-redun-sel", NULL)) {
+		rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-fuse-redun-sel", cpr_fuse_redun_sel, 5);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "qcom,cpr-fuse-redun-sel missing: rc=%d\n",
+				rc);
+			return rc;
+		}
+		cpr_vreg->cpr_fuse_redundant
+			= cpr_fuse_is_setting_expected(cpr_vreg,
+						cpr_fuse_redun_sel);
+	} else {
+		cpr_vreg->cpr_fuse_redundant = false;
+	}
+
+	if (cpr_vreg->cpr_fuse_redundant)
+		cpr_info(cpr_vreg, "using redundant fuse parameters\n");
+
+	return 0;
+}
+
+static int cpr_read_fuse_revision(struct platform_device *pdev,
+		     struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	u32 fuse_sel[4];
+	int rc;
+
+	if (of_find_property(of_node, "qcom,cpr-fuse-revision", NULL)) {
+		rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-fuse-revision", fuse_sel, 4);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "qcom,cpr-fuse-revision read failed: rc=%d\n",
+				rc);
+			return rc;
+		}
+		cpr_vreg->cpr_fuse_revision
+			= cpr_read_efuse_param(cpr_vreg, fuse_sel[0],
+					fuse_sel[1], fuse_sel[2], fuse_sel[3]);
+		cpr_info(cpr_vreg, "fuse revision = %d\n",
+			cpr_vreg->cpr_fuse_revision);
+	} else {
+		cpr_vreg->cpr_fuse_revision = FUSE_REVISION_UNKNOWN;
+	}
+
+	return 0;
+}
+
+static int cpr_read_ro_select(struct platform_device *pdev,
+				     struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int rc = 0;
+	u32 cpr_fuse_row[2];
+	char *ro_sel_str;
+	int *bp_ro_sel;
+	int i;
+
+	bp_ro_sel
+		= kzalloc((cpr_vreg->num_fuse_corners + 1) * sizeof(*bp_ro_sel),
+			GFP_KERNEL);
+	if (!bp_ro_sel) {
+		cpr_err(cpr_vreg, "could not allocate memory for temp array\n");
+		return -ENOMEM;
+	}
+
+	if (cpr_vreg->cpr_fuse_redundant) {
+		rc = of_property_read_u32_array(of_node,
+				"qcom,cpr-fuse-redun-row",
+				cpr_fuse_row, 2);
+		ro_sel_str = "qcom,cpr-fuse-redun-ro-sel";
+	} else {
+		rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-row",
+				cpr_fuse_row, 2);
+		ro_sel_str = "qcom,cpr-fuse-ro-sel";
+	}
+	if (rc)
+		goto error;
+
+	rc = of_property_read_u32_array(of_node, ro_sel_str,
+		&bp_ro_sel[CPR_FUSE_CORNER_MIN], cpr_vreg->num_fuse_corners);
+	if (rc) {
+		cpr_err(cpr_vreg, "%s read error, rc=%d\n", ro_sel_str, rc);
+		goto error;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
+		cpr_vreg->cpr_fuse_ro_sel[i]
+			= cpr_read_efuse_param(cpr_vreg, cpr_fuse_row[0],
+				bp_ro_sel[i], CPR_FUSE_RO_SEL_BITS,
+				cpr_fuse_row[1]);
+
+error:
+	kfree(bp_ro_sel);
+
+	return rc;
+}
+
+static int cpr_find_fuse_map_match(struct platform_device *pdev,
+				     struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int i, j, rc, tuple_size;
+	int len = 0;
+	u32 *tmp, val, ro;
+
+	/* Specify default no match case. */
+	cpr_vreg->cpr_fuse_map_match = FUSE_MAP_NO_MATCH;
+	cpr_vreg->cpr_fuse_map_count = 0;
+
+	if (!of_find_property(of_node, "qcom,cpr-fuse-version-map", &len)) {
+		/* No mapping present. */
+		return 0;
+	}
+
+	tuple_size = cpr_vreg->num_fuse_corners + 3;
+	cpr_vreg->cpr_fuse_map_count = len / (sizeof(u32) * tuple_size);
+
+	if (len == 0 || len % (sizeof(u32) * tuple_size)) {
+		cpr_err(cpr_vreg, "qcom,cpr-fuse-version-map length=%d is invalid\n",
+			len);
+		return -EINVAL;
+	}
+
+	tmp = kzalloc(len, GFP_KERNEL);
+	if (!tmp) {
+		cpr_err(cpr_vreg, "could not allocate memory for temp array\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-version-map",
+				tmp, cpr_vreg->cpr_fuse_map_count * tuple_size);
+	if (rc) {
+		cpr_err(cpr_vreg, "could not read qcom,cpr-fuse-version-map, rc=%d\n",
+			rc);
+		goto done;
+	}
+
+	/*
+	 * qcom,cpr-fuse-version-map tuple format:
+	 * <speed_bin, pvs_version, cpr_fuse_revision, ro_sel[1], ...,
+	 *  ro_sel[n]> for n == number of fuse corners
+	 */
+	for (i = 0; i < cpr_vreg->cpr_fuse_map_count; i++) {
+		if (tmp[i * tuple_size] != cpr_vreg->speed_bin
+		    && tmp[i * tuple_size] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		if (tmp[i * tuple_size + 1] != cpr_vreg->pvs_version
+		    && tmp[i * tuple_size + 1] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		if (tmp[i * tuple_size + 2] != cpr_vreg->cpr_fuse_revision
+		    && tmp[i * tuple_size + 2] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		for (j = 0; j < cpr_vreg->num_fuse_corners; j++) {
+			val = tmp[i * tuple_size + 3 + j];
+			ro = cpr_vreg->cpr_fuse_ro_sel[j + CPR_FUSE_CORNER_MIN];
+			if (val != ro && val != FUSE_PARAM_MATCH_ANY)
+				break;
+		}
+		if (j == cpr_vreg->num_fuse_corners) {
+			cpr_vreg->cpr_fuse_map_match = i;
+			break;
+		}
+	}
+
+	if (cpr_vreg->cpr_fuse_map_match != FUSE_MAP_NO_MATCH)
+		cpr_debug(cpr_vreg, "qcom,cpr-fuse-version-map tuple match found: %d\n",
+			cpr_vreg->cpr_fuse_map_match);
+	else
+		cpr_debug(cpr_vreg, "qcom,cpr-fuse-version-map tuple match not found\n");
+
+done:
+	kfree(tmp);
+	return rc;
+}
+
+static int cpr_minimum_quot_difference_adjustment(struct platform_device *pdev,
+					struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int tuple_count, tuple_match;
+	int rc, i, len = 0;
+	u32 index, adjust_quot = 0;
+	u32 *min_diff_quot;
+
+	if (!of_find_property(of_node, "qcom,cpr-fuse-min-quot-diff", NULL))
+		/* No conditional adjustment needed on revised quotients. */
+		return 0;
+
+	if (!of_find_property(of_node, "qcom,cpr-min-quot-diff-adjustment",
+						&len)) {
+		cpr_err(cpr_vreg, "qcom,cpr-min-quot-diff-adjustment not specified\n");
+		return -ENODEV;
+	}
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH)
+			/* No matching index to use for quotient adjustment. */
+			return 0;
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "qcom,cpr-min-quot-diff-adjustment length=%d is invalid\n",
+					len);
+		return -EINVAL;
+	}
+
+	min_diff_quot = kzalloc(cpr_vreg->num_fuse_corners * sizeof(u32),
+							GFP_KERNEL);
+	if (!min_diff_quot) {
+		cpr_err(cpr_vreg, "memory alloc failed\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-min-quot-diff",
+						min_diff_quot,
+						cpr_vreg->num_fuse_corners);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "qcom,cpr-fuse-min-quot-diff reading failed, rc = %d\n",
+							rc);
+		goto error;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN + 1;
+				i <= cpr_vreg->num_fuse_corners; i++) {
+		if ((cpr_vreg->cpr_fuse_target_quot[i]
+			- cpr_vreg->cpr_fuse_target_quot[i - 1])
+		    <= (int)min_diff_quot[i - CPR_FUSE_CORNER_MIN]) {
+			index = tuple_match * cpr_vreg->num_fuse_corners
+					+ i - CPR_FUSE_CORNER_MIN;
+			rc = of_property_read_u32_index(of_node,
+						"qcom,cpr-min-quot-diff-adjustment",
+						index, &adjust_quot);
+			if (rc) {
+				cpr_err(cpr_vreg, "could not read qcom,cpr-min-quot-diff-adjustment index %u, rc=%d\n",
+							index, rc);
+				goto error;
+			}
+
+			cpr_vreg->cpr_fuse_target_quot[i]
+				= cpr_vreg->cpr_fuse_target_quot[i - 1]
+					+ adjust_quot;
+			cpr_info(cpr_vreg, "Corner[%d]: revised adjusted quotient = %d\n",
+					i, cpr_vreg->cpr_fuse_target_quot[i]);
+		};
+	}
+
+error:
+	kfree(min_diff_quot);
+	return rc;
+}
+
+static int cpr_adjust_target_quots(struct platform_device *pdev,
+					struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int tuple_count, tuple_match, i;
+	u32 index;
+	u32 quot_adjust = 0;
+	int len = 0;
+	int rc = 0;
+
+	if (!of_find_property(of_node, "qcom,cpr-quotient-adjustment", &len)) {
+		/* No static quotient adjustment needed. */
+		return 0;
+	}
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/* No matching index to use for quotient adjustment. */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_fuse_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "qcom,cpr-quotient-adjustment length=%d is invalid\n",
+			len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		index = tuple_match * cpr_vreg->num_fuse_corners
+				+ i - CPR_FUSE_CORNER_MIN;
+		rc = of_property_read_u32_index(of_node,
+			"qcom,cpr-quotient-adjustment", index, &quot_adjust);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read qcom,cpr-quotient-adjustment index %u, rc=%d\n",
+				index, rc);
+			return rc;
+		}
+
+		if (quot_adjust) {
+			cpr_vreg->cpr_fuse_target_quot[i] += quot_adjust;
+			cpr_info(cpr_vreg, "Corner[%d]: adjusted target quot = %d\n",
+				i, cpr_vreg->cpr_fuse_target_quot[i]);
+		}
+	}
+
+	rc = cpr_minimum_quot_difference_adjustment(pdev, cpr_vreg);
+	if (rc)
+		cpr_err(cpr_vreg, "failed to apply minimum quot difference rc=%d\n",
+					rc);
+
+	return rc;
+}
+
+static int cpr_check_allowed(struct platform_device *pdev,
+					struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	char *allow_str = "qcom,cpr-allowed";
+	int rc = 0, count;
+	int tuple_count, tuple_match;
+	u32 allow_status;
+
+	if (!of_find_property(of_node, allow_str, &count))
+		/* CPR is allowed for all fuse revisions. */
+		return 0;
+
+	count /= sizeof(u32);
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH)
+			/* No matching index to use for CPR allowed. */
+			return 0;
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (count != tuple_count) {
+		cpr_err(cpr_vreg, "%s count=%d is invalid\n", allow_str,
+			count);
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_index(of_node, allow_str, tuple_match,
+		&allow_status);
+	if (rc) {
+		cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
+			allow_str, tuple_match, rc);
+		return rc;
+	}
+
+	if (allow_status && !cpr_vreg->cpr_fuse_disable)
+		cpr_vreg->cpr_fuse_disable = false;
+	else
+		cpr_vreg->cpr_fuse_disable = true;
+
+	cpr_info(cpr_vreg, "CPR closed loop is %s for fuse revision %d\n",
+		cpr_vreg->cpr_fuse_disable ? "disabled" : "enabled",
+		cpr_vreg->cpr_fuse_revision);
+
+	return rc;
+}
+
+static int cpr_check_de_aging_allowed(struct cpr_regulator *cpr_vreg,
+				struct device *dev)
+{
+	struct device_node *of_node = dev->of_node;
+	char *allow_str = "qcom,cpr-de-aging-allowed";
+	int rc = 0, count;
+	int tuple_count, tuple_match;
+	u32 allow_status = 0;
+
+	if (!of_find_property(of_node, allow_str, &count)) {
+		/* CPR de-aging is not allowed for all fuse revisions. */
+		return allow_status;
+	}
+
+	count /= sizeof(u32);
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH)
+			/* No matching index to use for CPR de-aging allowed. */
+			return 0;
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (count != tuple_count) {
+		cpr_err(cpr_vreg, "%s count=%d is invalid\n", allow_str,
+			count);
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_index(of_node, allow_str, tuple_match,
+		&allow_status);
+	if (rc) {
+		cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
+			allow_str, tuple_match, rc);
+		return rc;
+	}
+
+	cpr_info(cpr_vreg, "CPR de-aging is %s for fuse revision %d\n",
+			allow_status ? "allowed" : "not allowed",
+			cpr_vreg->cpr_fuse_revision);
+
+	return allow_status;
+}
+
+static int cpr_aging_init(struct platform_device *pdev,
+			struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	struct cpr_aging_info *aging_info;
+	struct cpr_aging_sensor_info *sensor_info;
+	int num_fuse_corners = cpr_vreg->num_fuse_corners;
+	int i, rc = 0, len = 0, num_aging_sensors, ro_sel, bits;
+	u32 *aging_sensor_id, *fuse_sel, *fuse_sel_orig;
+	u32 sensor = 0, non_collapsible_sensor_mask = 0;
+	u64 efuse_val;
+	struct property *prop;
+
+	if (!of_find_property(of_node, "qcom,cpr-aging-sensor-id", &len)) {
+		/* No CPR de-aging adjustments needed */
+		return 0;
+	}
+
+	if (len == 0) {
+		cpr_err(cpr_vreg, "qcom,cpr-aging-sensor-id property format is invalid\n");
+		return -EINVAL;
+	}
+	num_aging_sensors = len / sizeof(u32);
+	cpr_debug(cpr_vreg, "No of aging sensors = %d\n", num_aging_sensors);
+
+	if (cpumask_empty(&cpr_vreg->cpu_mask)) {
+		cpr_err(cpr_vreg, "qcom,cpr-cpus property missing\n");
+		return -EINVAL;
+	}
+
+	rc = cpr_check_de_aging_allowed(cpr_vreg, &pdev->dev);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "cpr_check_de_aging_allowed failed: rc=%d\n",
+			rc);
+		return rc;
+	} else if (rc == 0) {
+		/* CPR de-aging is not allowed for the current fuse combo */
+		return 0;
+	}
+
+	aging_info = devm_kzalloc(&pdev->dev, sizeof(*aging_info),
+				GFP_KERNEL);
+	if (!aging_info)
+		return -ENOMEM;
+
+	cpr_vreg->aging_info = aging_info;
+	aging_info->num_aging_sensors = num_aging_sensors;
+
+	rc = of_property_read_u32(of_node, "qcom,cpr-aging-ref-corner",
+			&aging_info->aging_corner);
+	if (rc) {
+		cpr_err(cpr_vreg, "qcom,cpr-aging-ref-corner missing rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-aging-ref-voltage",
+			&aging_info->aging_ref_voltage, rc);
+	if (rc)
+		return rc;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-max-aging-margin",
+			&aging_info->max_aging_margin, rc);
+	if (rc)
+		return rc;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-aging-ro-scaling-factor",
+			&aging_info->aging_ro_kv, rc);
+	if (rc)
+		return rc;
+
+	/* Check for DIV by 0 error */
+	if (aging_info->aging_ro_kv == 0) {
+		cpr_err(cpr_vreg, "invalid cpr-aging-ro-scaling-factor value: %u\n",
+			aging_info->aging_ro_kv);
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-ro-scaling-factor",
+			aging_info->cpr_ro_kv, CPR_NUM_RING_OSC);
+	if (rc) {
+		cpr_err(cpr_vreg, "qcom,cpr-ro-scaling-factor property read failed, rc = %d\n",
+			rc);
+		return rc;
+	}
+
+	if (of_find_property(of_node, "qcom,cpr-non-collapsible-sensors",
+				&len)) {
+		len = len / sizeof(u32);
+		if (len <= 0 || len > 32) {
+			cpr_err(cpr_vreg, "qcom,cpr-non-collapsible-sensors has an incorrect size\n");
+			return -EINVAL;
+		}
+
+		for (i = 0; i < len; i++) {
+			rc = of_property_read_u32_index(of_node,
+						"qcom,cpr-non-collapsible-sensors",
+						i, &sensor);
+			if (rc) {
+				cpr_err(cpr_vreg, "could not read qcom,cpr-non-collapsible-sensors index %u, rc=%d\n",
+					i, rc);
+				return rc;
+			}
+
+			if (sensor > 31) {
+				cpr_err(cpr_vreg, "invalid non-collapsible sensor = %u\n",
+					sensor);
+				return -EINVAL;
+			}
+
+			non_collapsible_sensor_mask |= BIT(sensor);
+		}
+
+		/*
+		 * Bypass the sensors in collapsible domain for
+		 * de-aging measurements
+		 */
+		aging_info->aging_sensor_bypass =
+						~(non_collapsible_sensor_mask);
+		cpr_debug(cpr_vreg, "sensor bypass mask for aging = 0x%08x\n",
+			aging_info->aging_sensor_bypass);
+	}
+
+	prop = of_find_property(pdev->dev.of_node, "qcom,cpr-aging-derate",
+			NULL);
+	if ((!prop) ||
+		(prop->length != num_fuse_corners * sizeof(u32))) {
+		cpr_err(cpr_vreg, "qcom,cpr-aging-derate incorrectly configured\n");
+		return -EINVAL;
+	}
+
+	aging_sensor_id = kcalloc(num_aging_sensors, sizeof(*aging_sensor_id),
+				GFP_KERNEL);
+	fuse_sel = kcalloc(num_aging_sensors * 4, sizeof(*fuse_sel),
+				GFP_KERNEL);
+	aging_info->voltage_adjust = devm_kcalloc(&pdev->dev,
+					num_fuse_corners + 1,
+					sizeof(*aging_info->voltage_adjust),
+					GFP_KERNEL);
+	aging_info->sensor_info = devm_kcalloc(&pdev->dev, num_aging_sensors,
+					sizeof(*aging_info->sensor_info),
+					GFP_KERNEL);
+	aging_info->aging_derate = devm_kcalloc(&pdev->dev,
+					num_fuse_corners + 1,
+					sizeof(*aging_info->aging_derate),
+					GFP_KERNEL);
+
+	if (!aging_info->aging_derate || !aging_sensor_id
+		|| !aging_info->sensor_info || !fuse_sel
+		|| !aging_info->voltage_adjust)
+		goto err;
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-aging-sensor-id",
+					aging_sensor_id, num_aging_sensors);
+	if (rc) {
+		cpr_err(cpr_vreg, "qcom,cpr-aging-sensor-id property read failed, rc = %d\n",
+				rc);
+		goto err;
+	}
+
+	for (i = 0; i < num_aging_sensors; i++)
+		if (aging_sensor_id[i] < 0 || aging_sensor_id[i] > 31) {
+			cpr_err(cpr_vreg, "Invalid aging sensor id: %u\n",
+				aging_sensor_id[i]);
+			rc = -EINVAL;
+			goto err;
+		}
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-aging-derate",
+			&aging_info->aging_derate[CPR_FUSE_CORNER_MIN],
+			num_fuse_corners);
+	if (rc) {
+		cpr_err(cpr_vreg, "qcom,cpr-aging-derate property read failed, rc = %d\n",
+				rc);
+		goto err;
+	}
+
+	rc = of_property_read_u32_array(of_node,
+				"qcom,cpr-fuse-aging-init-quot-diff",
+				fuse_sel, (num_aging_sensors * 4));
+	if (rc) {
+		cpr_err(cpr_vreg, "qcom,cpr-fuse-aging-init-quot-diff read failed, rc = %d\n",
+				rc);
+		goto err;
+	}
+
+	fuse_sel_orig = fuse_sel;
+	sensor_info = aging_info->sensor_info;
+	for (i = 0; i < num_aging_sensors; i++, sensor_info++) {
+		sensor_info->sensor_id = aging_sensor_id[i];
+		efuse_val = cpr_read_efuse_param(cpr_vreg, fuse_sel[0],
+				fuse_sel[1], fuse_sel[2], fuse_sel[3]);
+		bits = fuse_sel[2];
+		sensor_info->initial_quot_diff = ((efuse_val & BIT(bits - 1)) ?
+			-1 : 1) * (efuse_val & (BIT(bits - 1) - 1));
+
+		cpr_debug(cpr_vreg, "Age sensor[%d] Initial quot diff = %d\n",
+				sensor_info->sensor_id,
+				sensor_info->initial_quot_diff);
+		fuse_sel += 4;
+	}
+
+	/*
+	 * Add max aging margin here. This can be adjusted later in
+	 * de-aging algorithm.
+	 */
+	for (i = CPR_FUSE_CORNER_MIN; i <= num_fuse_corners; i++) {
+		ro_sel = cpr_vreg->cpr_fuse_ro_sel[i];
+		cpr_vreg->cpr_fuse_target_quot[i] +=
+				(aging_info->cpr_ro_kv[ro_sel]
+				* aging_info->max_aging_margin) / 1000000;
+		aging_info->voltage_adjust[i] = aging_info->max_aging_margin;
+		cpr_info(cpr_vreg, "Corner[%d]: age margin adjusted quotient = %d\n",
+			i, cpr_vreg->cpr_fuse_target_quot[i]);
+	}
+
+	kfree(fuse_sel_orig);
+err:
+	kfree(aging_sensor_id);
+	return rc;
+}
+
+static int cpr_cpu_map_init(struct cpr_regulator *cpr_vreg, struct device *dev)
+{
+	struct device_node *cpu_node;
+	int i, cpu;
+
+	if (!of_find_property(dev->of_node, "qcom,cpr-cpus",
+				&cpr_vreg->num_adj_cpus)) {
+		/* No adjustments based on online cores */
+		return 0;
+	}
+	cpr_vreg->num_adj_cpus /= sizeof(u32);
+
+	cpr_vreg->adj_cpus = devm_kcalloc(dev, cpr_vreg->num_adj_cpus,
+					sizeof(int), GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus)
+		return -ENOMEM;
+
+	for (i = 0; i < cpr_vreg->num_adj_cpus; i++) {
+		cpu_node = of_parse_phandle(dev->of_node, "qcom,cpr-cpus", i);
+		if (!cpu_node) {
+			cpr_err(cpr_vreg, "could not find CPU node %d\n", i);
+			return -EINVAL;
+		}
+		cpr_vreg->adj_cpus[i] = -1;
+		for_each_possible_cpu(cpu) {
+			if (of_get_cpu_node(cpu, NULL) == cpu_node) {
+				cpr_vreg->adj_cpus[i] = cpu;
+				cpumask_set_cpu(cpu, &cpr_vreg->cpu_mask);
+				break;
+			}
+		}
+		of_node_put(cpu_node);
+	}
+
+	return 0;
+}
+
+static int cpr_init_cpr_efuse(struct platform_device *pdev,
+				     struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int i, rc = 0;
+	bool scheme_fuse_valid = false;
+	bool disable_fuse_valid = false;
+	char *targ_quot_str;
+	u32 cpr_fuse_row[2];
+	u32 bp_cpr_disable, bp_scheme;
+	size_t len;
+	int *bp_target_quot;
+	u64 fuse_bits, fuse_bits_2;
+	u32 *target_quot_size;
+	struct cpr_quot_scale *quot_scale;
+
+	len = cpr_vreg->num_fuse_corners + 1;
+
+	bp_target_quot = kzalloc(len * sizeof(*bp_target_quot), GFP_KERNEL);
+	target_quot_size = kzalloc(len * sizeof(*target_quot_size), GFP_KERNEL);
+	quot_scale = kzalloc(len * sizeof(*quot_scale), GFP_KERNEL);
+
+	if (!bp_target_quot || !target_quot_size || !quot_scale) {
+		cpr_err(cpr_vreg,
+			"Could not allocate memory for fuse parsing arrays\n");
+		rc = -ENOMEM;
+		goto error;
+	}
+
+	if (cpr_vreg->cpr_fuse_redundant) {
+		rc = of_property_read_u32_array(of_node,
+				"qcom,cpr-fuse-redun-row",
+				cpr_fuse_row, 2);
+		targ_quot_str = "qcom,cpr-fuse-redun-target-quot";
+	} else {
+		rc = of_property_read_u32_array(of_node, "qcom,cpr-fuse-row",
+				cpr_fuse_row, 2);
+		targ_quot_str = "qcom,cpr-fuse-target-quot";
+	}
+	if (rc)
+		goto error;
+
+	rc = of_property_read_u32_array(of_node, targ_quot_str,
+		&bp_target_quot[CPR_FUSE_CORNER_MIN],
+		cpr_vreg->num_fuse_corners);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "missing %s: rc=%d\n", targ_quot_str, rc);
+		goto error;
+	}
+
+	if (of_find_property(of_node, "qcom,cpr-fuse-target-quot-size", NULL)) {
+		rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-fuse-target-quot-size",
+			&target_quot_size[CPR_FUSE_CORNER_MIN],
+			cpr_vreg->num_fuse_corners);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-size: rc=%d\n",
+				rc);
+			goto error;
+		}
+	} else {
+		/*
+		 * Default fuse quotient parameter size to match target register
+		 * size.
+		 */
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++)
+			target_quot_size[i] = CPR_FUSE_TARGET_QUOT_BITS;
+	}
+
+	if (of_find_property(of_node, "qcom,cpr-fuse-target-quot-scale",
+				NULL)) {
+		for (i = 0; i < cpr_vreg->num_fuse_corners; i++) {
+			rc = of_property_read_u32_index(of_node,
+				"qcom,cpr-fuse-target-quot-scale", i * 2,
+				&quot_scale[i + CPR_FUSE_CORNER_MIN].offset);
+			if (rc < 0) {
+				cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-scale: rc=%d\n",
+					rc);
+				goto error;
+			}
+
+			rc = of_property_read_u32_index(of_node,
+				"qcom,cpr-fuse-target-quot-scale", i * 2 + 1,
+			       &quot_scale[i + CPR_FUSE_CORNER_MIN].multiplier);
+			if (rc < 0) {
+				cpr_err(cpr_vreg, "error while reading qcom,cpr-fuse-target-quot-scale: rc=%d\n",
+					rc);
+				goto error;
+			}
+		}
+	} else {
+		/*
+		 * In the default case, target quotients require no scaling so
+		 * use offset = 0, multiplier = 1.
+		 */
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++) {
+			quot_scale[i].offset = 0;
+			quot_scale[i].multiplier = 1;
+		}
+	}
+
+	/* Read the control bits of eFuse */
+	fuse_bits = cpr_read_efuse_row(cpr_vreg, cpr_fuse_row[0],
+					cpr_fuse_row[1]);
+	cpr_info(cpr_vreg, "[row:%d] = 0x%llx\n", cpr_fuse_row[0], fuse_bits);
+
+	if (cpr_vreg->cpr_fuse_redundant) {
+		if (of_find_property(of_node,
+				"qcom,cpr-fuse-redun-bp-cpr-disable", NULL)) {
+			CPR_PROP_READ_U32(cpr_vreg, of_node,
+					  "cpr-fuse-redun-bp-cpr-disable",
+					  &bp_cpr_disable, rc);
+			disable_fuse_valid = true;
+			if (of_find_property(of_node,
+					"qcom,cpr-fuse-redun-bp-scheme",
+					NULL)) {
+				CPR_PROP_READ_U32(cpr_vreg, of_node,
+						"cpr-fuse-redun-bp-scheme",
+						&bp_scheme, rc);
+				scheme_fuse_valid = true;
+			}
+			if (rc)
+				goto error;
+			fuse_bits_2 = fuse_bits;
+		} else {
+			u32 temp_row[2];
+
+			/* Use original fuse if no optional property */
+			if (of_find_property(of_node,
+					"qcom,cpr-fuse-bp-cpr-disable", NULL)) {
+				CPR_PROP_READ_U32(cpr_vreg, of_node,
+					"cpr-fuse-bp-cpr-disable",
+					&bp_cpr_disable, rc);
+				disable_fuse_valid = true;
+			}
+			if (of_find_property(of_node,
+					"qcom,cpr-fuse-bp-scheme",
+					NULL)) {
+				CPR_PROP_READ_U32(cpr_vreg, of_node,
+						"cpr-fuse-bp-scheme",
+						&bp_scheme, rc);
+				scheme_fuse_valid = true;
+			}
+			rc = of_property_read_u32_array(of_node,
+					"qcom,cpr-fuse-row",
+					temp_row, 2);
+			if (rc)
+				goto error;
+
+			fuse_bits_2 = cpr_read_efuse_row(cpr_vreg, temp_row[0],
+							temp_row[1]);
+			cpr_info(cpr_vreg, "[original row:%d] = 0x%llx\n",
+				temp_row[0], fuse_bits_2);
+		}
+	} else {
+		if (of_find_property(of_node, "qcom,cpr-fuse-bp-cpr-disable",
+					NULL)) {
+			CPR_PROP_READ_U32(cpr_vreg, of_node,
+				"cpr-fuse-bp-cpr-disable", &bp_cpr_disable, rc);
+			disable_fuse_valid = true;
+		}
+		if (of_find_property(of_node, "qcom,cpr-fuse-bp-scheme",
+							NULL)) {
+			CPR_PROP_READ_U32(cpr_vreg, of_node,
+					"cpr-fuse-bp-scheme", &bp_scheme, rc);
+			scheme_fuse_valid = true;
+		}
+		if (rc)
+			goto error;
+		fuse_bits_2 = fuse_bits;
+	}
+
+	if (disable_fuse_valid) {
+		cpr_vreg->cpr_fuse_disable =
+					(fuse_bits_2 >> bp_cpr_disable) & 0x01;
+		cpr_info(cpr_vreg, "CPR disable fuse = %d\n",
+			cpr_vreg->cpr_fuse_disable);
+	} else {
+		cpr_vreg->cpr_fuse_disable = false;
+	}
+
+	if (scheme_fuse_valid) {
+		cpr_vreg->cpr_fuse_local = (fuse_bits_2 >> bp_scheme) & 0x01;
+		cpr_info(cpr_vreg, "local = %d\n", cpr_vreg->cpr_fuse_local);
+	} else {
+		cpr_vreg->cpr_fuse_local = true;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		cpr_vreg->cpr_fuse_target_quot[i]
+			= cpr_read_efuse_param(cpr_vreg, cpr_fuse_row[0],
+				bp_target_quot[i], target_quot_size[i],
+				cpr_fuse_row[1]);
+		/* Unpack the target quotient by scaling. */
+		cpr_vreg->cpr_fuse_target_quot[i] *= quot_scale[i].multiplier;
+		cpr_vreg->cpr_fuse_target_quot[i] += quot_scale[i].offset;
+		cpr_info(cpr_vreg,
+			"Corner[%d]: ro_sel = %d, target quot = %d\n", i,
+			cpr_vreg->cpr_fuse_ro_sel[i],
+			cpr_vreg->cpr_fuse_target_quot[i]);
+	}
+
+	rc = cpr_cpu_map_init(cpr_vreg, &pdev->dev);
+	if (rc) {
+		cpr_err(cpr_vreg, "CPR cpu map init failed: rc=%d\n", rc);
+		goto error;
+	}
+
+	rc = cpr_aging_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "CPR aging init failed: rc=%d\n", rc);
+		goto error;
+	}
+
+	rc = cpr_adjust_target_quots(pdev, cpr_vreg);
+	if (rc)
+		goto error;
+
+	for (i = CPR_FUSE_CORNER_MIN + 1;
+				i <= cpr_vreg->num_fuse_corners; i++) {
+		if (cpr_vreg->cpr_fuse_target_quot[i]
+				< cpr_vreg->cpr_fuse_target_quot[i - 1] &&
+			cpr_vreg->cpr_fuse_ro_sel[i] ==
+				cpr_vreg->cpr_fuse_ro_sel[i - 1]) {
+			cpr_vreg->cpr_fuse_disable = true;
+			cpr_err(cpr_vreg, "invalid quotient values; permanently disabling CPR\n");
+		}
+	}
+
+	if (cpr_vreg->flags & FLAGS_UPLIFT_QUOT_VOLT) {
+		cpr_voltage_uplift_wa_inc_quot(cpr_vreg, of_node);
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++) {
+			cpr_info(cpr_vreg,
+				"Corner[%d]: uplifted target quot = %d\n",
+				i, cpr_vreg->cpr_fuse_target_quot[i]);
+		}
+	}
+
+	/*
+	 * Check whether the fuse-quot-offset is defined per fuse corner.
+	 * If it is defined, use it (quot_offset) in the calculation
+	 * below for obtaining scaling factor per fuse corner.
+	 */
+	rc = cpr_get_fuse_quot_offset(cpr_vreg, pdev, quot_scale);
+	if (rc < 0)
+		goto error;
+
+	rc = cpr_get_corner_quot_adjustment(cpr_vreg, &pdev->dev);
+	if (rc)
+		goto error;
+
+	cpr_vreg->cpr_fuse_bits = fuse_bits;
+	if (!cpr_vreg->cpr_fuse_bits) {
+		cpr_vreg->cpr_fuse_disable = true;
+		cpr_err(cpr_vreg,
+			"cpr_fuse_bits == 0; permanently disabling CPR\n");
+	} else if (!cpr_vreg->fuse_quot_offset) {
+		/*
+		 * Check if the target quotients for the highest two fuse
+		 * corners are too close together.
+		 */
+		int *quot = cpr_vreg->cpr_fuse_target_quot;
+		int highest_fuse_corner = cpr_vreg->num_fuse_corners;
+		u32 min_diff_quot;
+		bool valid_fuse = true;
+
+		min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF;
+		of_property_read_u32(of_node, "qcom,cpr-quot-min-diff",
+							&min_diff_quot);
+
+		if (quot[highest_fuse_corner] > quot[highest_fuse_corner - 1]) {
+			if ((quot[highest_fuse_corner]
+				- quot[highest_fuse_corner - 1])
+					<= min_diff_quot)
+				valid_fuse = false;
+		} else {
+			valid_fuse = false;
+		}
+
+		if (!valid_fuse) {
+			cpr_vreg->cpr_fuse_disable = true;
+			cpr_err(cpr_vreg, "invalid quotient values; permanently disabling CPR\n");
+		}
+	}
+	rc = cpr_check_allowed(pdev, cpr_vreg);
+
+error:
+	kfree(bp_target_quot);
+	kfree(target_quot_size);
+	kfree(quot_scale);
+
+	return rc;
+}
+
+static int cpr_init_cpr_voltages(struct cpr_regulator *cpr_vreg,
+			struct device *dev)
+{
+	int i;
+	int size = cpr_vreg->num_corners + 1;
+
+	cpr_vreg->last_volt = devm_kzalloc(dev, sizeof(int) * size, GFP_KERNEL);
+	if (!cpr_vreg->last_volt)
+		return -EINVAL;
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
+		cpr_vreg->last_volt[i] = cpr_vreg->open_loop_volt[i];
+
+	return 0;
+}
+
+/*
+ * This function fills the virtual_limit array with voltages read from the
+ * prop_name device tree property if a given tuple in the property matches
+ * the speedbin and PVS version fuses found on the chip.  Otherwise,
+ * it fills the virtual_limit_array with corresponding values from the
+ * fuse_limit_array.
+ */
+static int cpr_fill_override_voltage(struct cpr_regulator *cpr_vreg,
+		struct device *dev, const char *prop_name, const char *label,
+		int *virtual_limit, int *fuse_limit)
+{
+	int rc = 0;
+	int i, j, size, pos;
+	struct property *prop;
+	bool match_found = false;
+	size_t buflen;
+	char *buf;
+	u32 *tmp;
+
+	prop = of_find_property(dev->of_node, prop_name, NULL);
+	if (!prop)
+		goto use_fuse_corner_limits;
+
+	size = prop->length / sizeof(u32);
+	if (size == 0 || size % (cpr_vreg->num_corners + 2)) {
+		cpr_err(cpr_vreg, "%s property format is invalid; reusing per-fuse-corner limits\n",
+			prop_name);
+		goto use_fuse_corner_limits;
+	}
+
+	tmp = kzalloc(size * sizeof(u32), GFP_KERNEL);
+	if (!tmp) {
+		cpr_err(cpr_vreg, "memory alloc failed\n");
+		return -ENOMEM;
+	}
+	rc = of_property_read_u32_array(dev->of_node, prop_name, tmp, size);
+	if (rc < 0) {
+		kfree(tmp);
+		cpr_err(cpr_vreg, "%s reading failed, rc = %d\n", prop_name,
+			rc);
+		return rc;
+	}
+
+	/*
+	 * Get limit voltage for each virtual corner based upon the speed_bin
+	 * and pvs_version values.
+	 */
+	for (i = 0; i < size; i += cpr_vreg->num_corners + 2) {
+		if (tmp[i] != cpr_vreg->speed_bin &&
+		    tmp[i] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		if (tmp[i + 1] != cpr_vreg->pvs_version &&
+		    tmp[i + 1] != FUSE_PARAM_MATCH_ANY)
+			continue;
+		for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++)
+			virtual_limit[j] = tmp[i + 2 + j - CPR_FUSE_CORNER_MIN];
+		match_found = true;
+		break;
+	}
+	kfree(tmp);
+
+	if (!match_found)
+		goto use_fuse_corner_limits;
+
+	/*
+	 * Log per-virtual-corner voltage limits since they are useful for
+	 * baseline CPR debugging.
+	 */
+	buflen = cpr_vreg->num_corners * (MAX_CHARS_PER_INT + 2) * sizeof(*buf);
+	buf = kzalloc(buflen, GFP_KERNEL);
+	if (buf == NULL) {
+		cpr_err(cpr_vreg, "Could not allocate memory for corner limit voltage logging\n");
+		return 0;
+	}
+
+	for (i = CPR_CORNER_MIN, pos = 0; i <= cpr_vreg->num_corners; i++)
+		pos += scnprintf(buf + pos, buflen - pos, "%d%s",
+			virtual_limit[i], i < cpr_vreg->num_corners ? " " : "");
+	cpr_info(cpr_vreg, "%s override voltage: [%s] uV\n", label, buf);
+	kfree(buf);
+
+	return rc;
+
+use_fuse_corner_limits:
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++)
+		virtual_limit[i] = fuse_limit[cpr_vreg->corner_map[i]];
+	return rc;
+}
+
+/*
+ * This function loads per-virtual-corner ceiling and floor voltages from device
+ * tree if their respective device tree properties are present.  These limits
+ * override those found in the per-fuse-corner arrays fuse_ceiling_volt and
+ * fuse_floor_volt.
+ */
+static int cpr_init_ceiling_floor_override_voltages(
+	struct cpr_regulator *cpr_vreg, struct device *dev)
+{
+	int rc, i;
+	int size = cpr_vreg->num_corners + 1;
+
+	cpr_vreg->ceiling_volt = devm_kzalloc(dev, sizeof(int) * size,
+						GFP_KERNEL);
+	cpr_vreg->floor_volt = devm_kzalloc(dev, sizeof(int) * size,
+						GFP_KERNEL);
+	cpr_vreg->cpr_max_ceiling = devm_kzalloc(dev, sizeof(int) * size,
+						GFP_KERNEL);
+	if (!cpr_vreg->ceiling_volt || !cpr_vreg->floor_volt ||
+		!cpr_vreg->cpr_max_ceiling)
+		return -ENOMEM;
+
+	rc = cpr_fill_override_voltage(cpr_vreg, dev,
+		"qcom,cpr-voltage-ceiling-override", "ceiling",
+		cpr_vreg->ceiling_volt, cpr_vreg->fuse_ceiling_volt);
+	if (rc)
+		return rc;
+
+	rc = cpr_fill_override_voltage(cpr_vreg, dev,
+		"qcom,cpr-voltage-floor-override", "floor",
+		cpr_vreg->floor_volt, cpr_vreg->fuse_floor_volt);
+	if (rc)
+		return rc;
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		if (cpr_vreg->floor_volt[i] > cpr_vreg->ceiling_volt[i]) {
+			cpr_err(cpr_vreg, "virtual corner %d floor=%d uV > ceiling=%d uV\n",
+				i, cpr_vreg->floor_volt[i],
+				cpr_vreg->ceiling_volt[i]);
+			return -EINVAL;
+		}
+
+		if (cpr_vreg->ceiling_max < cpr_vreg->ceiling_volt[i])
+			cpr_vreg->ceiling_max = cpr_vreg->ceiling_volt[i];
+		cpr_vreg->cpr_max_ceiling[i] = cpr_vreg->ceiling_volt[i];
+	}
+
+	return rc;
+}
+
+/*
+ * This function computes the per-virtual-corner floor voltages from
+ * per-virtual-corner ceiling voltages with an offset specified by a
+ * device-tree property. This must be called after open-loop voltage
+ * scaling, floor_volt array loading and the ceiling voltage is
+ * conditionally reduced to the open-loop voltage. It selects the
+ * maximum value between the calculated floor voltage values and
+ * the floor_volt array values and stores them in the floor_volt array.
+ */
+static int cpr_init_floor_to_ceiling_range(
+	struct cpr_regulator *cpr_vreg, struct device *dev)
+{
+	int rc, i, tuple_count, tuple_match, len, pos;
+	u32 index, floor_volt_adjust = 0;
+	char *prop_str, *buf;
+	size_t buflen;
+
+	prop_str = "qcom,cpr-floor-to-ceiling-max-range";
+
+	if (!of_find_property(dev->of_node, prop_str, &len))
+		return 0;
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/*
+			 * No matching index to use for floor-to-ceiling
+			 * max range.
+			 */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	if (len != cpr_vreg->num_corners * tuple_count * sizeof(u32)) {
+		cpr_err(cpr_vreg, "%s length=%d is invalid\n", prop_str, len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_CORNER_MIN; i <= cpr_vreg->num_corners; i++) {
+		index = tuple_match * cpr_vreg->num_corners
+				+ i - CPR_CORNER_MIN;
+		rc = of_property_read_u32_index(dev->of_node, prop_str,
+			index, &floor_volt_adjust);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read %s index %u, rc=%d\n",
+				prop_str, index, rc);
+			return rc;
+		}
+
+		if ((int)floor_volt_adjust >= 0) {
+			cpr_vreg->floor_volt[i] = max(cpr_vreg->floor_volt[i],
+						(cpr_vreg->ceiling_volt[i]
+						- (int)floor_volt_adjust));
+			cpr_vreg->floor_volt[i]
+					= DIV_ROUND_UP(cpr_vreg->floor_volt[i],
+							cpr_vreg->step_volt) *
+							cpr_vreg->step_volt;
+			if (cpr_vreg->open_loop_volt[i]
+					< cpr_vreg->floor_volt[i])
+				cpr_vreg->open_loop_volt[i]
+						= cpr_vreg->floor_volt[i];
+		}
+	}
+
+	/*
+	 * Log per-virtual-corner voltage limits resulted after considering the
+	 * floor-to-ceiling max range since they are useful for baseline CPR
+	 * debugging.
+	 */
+	buflen = cpr_vreg->num_corners * (MAX_CHARS_PER_INT + 2) * sizeof(*buf);
+	buf = kzalloc(buflen, GFP_KERNEL);
+	if (buf == NULL) {
+		cpr_err(cpr_vreg, "Could not allocate memory for corner limit voltage logging\n");
+		return 0;
+	}
+
+	for (i = CPR_CORNER_MIN, pos = 0; i <= cpr_vreg->num_corners; i++)
+		pos += scnprintf(buf + pos, buflen - pos, "%d%s",
+			cpr_vreg->floor_volt[i],
+			i < cpr_vreg->num_corners ? " " : "");
+	cpr_info(cpr_vreg, "Final floor override voltages: [%s] uV\n", buf);
+	kfree(buf);
+
+	return 0;
+}
+
+static int cpr_init_step_quotient(struct platform_device *pdev,
+		  struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int len = 0;
+	u32 step_quot[CPR_NUM_RING_OSC];
+	int i, rc;
+
+	if (!of_find_property(of_node, "qcom,cpr-step-quotient", &len)) {
+		cpr_err(cpr_vreg, "qcom,cpr-step-quotient property missing\n");
+		return -EINVAL;
+	}
+
+	if (len == sizeof(u32)) {
+		/* Single step quotient used for all ring oscillators. */
+		rc = of_property_read_u32(of_node, "qcom,cpr-step-quotient",
+					step_quot);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read qcom,cpr-step-quotient, rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++)
+			cpr_vreg->step_quotient[i] = step_quot[0];
+	} else if (len == sizeof(u32) * CPR_NUM_RING_OSC) {
+		/* Unique step quotient used per ring oscillator. */
+		rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-step-quotient", step_quot, CPR_NUM_RING_OSC);
+		if (rc) {
+			cpr_err(cpr_vreg, "could not read qcom,cpr-step-quotient, rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++)
+			cpr_vreg->step_quotient[i]
+				= step_quot[cpr_vreg->cpr_fuse_ro_sel[i]];
+	} else {
+		cpr_err(cpr_vreg, "qcom,cpr-step-quotient has invalid length=%d\n",
+			len);
+		return -EINVAL;
+	}
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++)
+		cpr_debug(cpr_vreg, "step_quotient[%d]=%u\n", i,
+			cpr_vreg->step_quotient[i]);
+
+	return 0;
+}
+
+static int cpr_init_cpr_parameters(struct platform_device *pdev,
+					  struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int rc = 0;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-ref-clk",
+			  &cpr_vreg->ref_clk_khz, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-delay",
+			  &cpr_vreg->timer_delay_us, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-cons-up",
+			  &cpr_vreg->timer_cons_up, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-timer-cons-down",
+			  &cpr_vreg->timer_cons_down, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-irq-line",
+			  &cpr_vreg->irq_line, rc);
+	if (rc)
+		return rc;
+
+	rc = cpr_init_step_quotient(pdev, cpr_vreg);
+	if (rc)
+		return rc;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-up-threshold",
+			  &cpr_vreg->up_threshold, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-down-threshold",
+			  &cpr_vreg->down_threshold, rc);
+	if (rc)
+		return rc;
+	cpr_info(cpr_vreg, "up threshold = %u, down threshold = %u\n",
+		cpr_vreg->up_threshold, cpr_vreg->down_threshold);
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-idle-clocks",
+			  &cpr_vreg->idle_clocks, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-gcnt-time",
+			  &cpr_vreg->gcnt_time_us, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "vdd-apc-step-up-limit",
+			  &cpr_vreg->vdd_apc_step_up_limit, rc);
+	if (rc)
+		return rc;
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "vdd-apc-step-down-limit",
+			  &cpr_vreg->vdd_apc_step_down_limit, rc);
+	if (rc)
+		return rc;
+
+	rc = of_property_read_u32(of_node, "qcom,cpr-clamp-timer-interval",
+				  &cpr_vreg->clamp_timer_interval);
+	if (rc && rc != -EINVAL) {
+		cpr_err(cpr_vreg,
+			"error reading qcom,cpr-clamp-timer-interval, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	cpr_vreg->clamp_timer_interval = min(cpr_vreg->clamp_timer_interval,
+					(u32)RBIF_TIMER_ADJ_CLAMP_INT_MASK);
+
+	/* Init module parameter with the DT value */
+	cpr_vreg->enable = of_property_read_bool(of_node, "qcom,cpr-enable");
+	cpr_info(cpr_vreg, "CPR is %s by default.\n",
+		cpr_vreg->enable ? "enabled" : "disabled");
+
+	return 0;
+}
+
+static void cpr_regulator_switch_adj_cpus(struct cpr_regulator *cpr_vreg)
+{
+	cpr_vreg->last_volt = cpr_vreg->adj_cpus_last_volt
+					[cpr_vreg->online_cpus];
+	cpr_vreg->save_ctl = cpr_vreg->adj_cpus_save_ctl[cpr_vreg->online_cpus];
+	cpr_vreg->save_irq = cpr_vreg->adj_cpus_save_irq[cpr_vreg->online_cpus];
+
+	if (cpr_vreg->adj_cpus_quot_adjust)
+		cpr_vreg->quot_adjust = cpr_vreg->adj_cpus_quot_adjust
+						[cpr_vreg->online_cpus];
+	if (cpr_vreg->adj_cpus_open_loop_volt)
+		cpr_vreg->open_loop_volt
+			= cpr_vreg->adj_cpus_open_loop_volt
+				[cpr_vreg->online_cpus];
+	if (cpr_vreg->adj_cpus_open_loop_volt_as_ceiling)
+		cpr_vreg->ceiling_volt = cpr_vreg->open_loop_volt;
+}
+
+static void cpr_regulator_set_online_cpus(struct cpr_regulator *cpr_vreg)
+{
+	int i, j;
+
+	cpr_vreg->online_cpus = 0;
+	get_online_cpus();
+	for_each_online_cpu(i)
+		for (j = 0; j < cpr_vreg->num_adj_cpus; j++)
+			if (i == cpr_vreg->adj_cpus[j])
+				cpr_vreg->online_cpus++;
+	put_online_cpus();
+}
+
+static int cpr_regulator_cpu_callback(struct notifier_block *nb,
+					    unsigned long action, void *data)
+{
+	struct cpr_regulator *cpr_vreg = container_of(nb, struct cpr_regulator,
+					cpu_notifier);
+	int cpu = (long)data;
+	int prev_online_cpus, rc, i;
+
+	action &= ~CPU_TASKS_FROZEN;
+
+	if (action != CPU_UP_PREPARE && action != CPU_UP_CANCELED
+	    && action != CPU_DEAD)
+		return NOTIFY_OK;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	if (cpr_vreg->skip_voltage_change_during_suspend
+	    && cpr_vreg->is_cpr_suspended) {
+		/* Do nothing during system suspend/resume */
+		goto done;
+	}
+
+	prev_online_cpus = cpr_vreg->online_cpus;
+	cpr_regulator_set_online_cpus(cpr_vreg);
+
+	if (action == CPU_UP_PREPARE)
+		for (i = 0; i < cpr_vreg->num_adj_cpus; i++)
+			if (cpu == cpr_vreg->adj_cpus[i]) {
+				cpr_vreg->online_cpus++;
+				break;
+			}
+
+	if (cpr_vreg->online_cpus == prev_online_cpus)
+		goto done;
+
+	cpr_debug(cpr_vreg, "adjusting corner %d quotient for %d cpus\n",
+		cpr_vreg->corner, cpr_vreg->online_cpus);
+
+	cpr_regulator_switch_adj_cpus(cpr_vreg);
+
+	if (cpr_vreg->corner) {
+		rc = cpr_regulator_set_voltage(cpr_vreg->rdev,
+				cpr_vreg->corner, true);
+		if (rc)
+			cpr_err(cpr_vreg, "could not update quotient, rc=%d\n",
+				rc);
+	}
+
+done:
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+	return NOTIFY_OK;
+}
+
+static void cpr_pm_disable(struct cpr_regulator *cpr_vreg, bool disable)
+{
+	u32 reg_val;
+
+	if (cpr_vreg->is_cpr_suspended)
+		return;
+
+	reg_val = cpr_read(cpr_vreg, REG_RBCPR_CTL);
+
+	if (disable) {
+		/* Proceed only if CPR is enabled */
+		if (!(reg_val & RBCPR_CTL_LOOP_EN))
+			return;
+		cpr_ctl_disable(cpr_vreg);
+		cpr_vreg->cpr_disabled_in_pc = true;
+	} else {
+		/* Proceed only if CPR was disabled in PM_ENTER */
+		if (!cpr_vreg->cpr_disabled_in_pc)
+			return;
+		cpr_vreg->cpr_disabled_in_pc = false;
+		cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
+	}
+
+	/* Make sure register write is complete */
+	mb();
+}
+
+static int cpr_pm_callback(struct notifier_block *nb,
+			    unsigned long action, void *data)
+{
+	struct cpr_regulator *cpr_vreg = container_of(nb,
+			struct cpr_regulator, pm_notifier);
+
+	if (action != CPU_PM_ENTER && action != CPU_PM_ENTER_FAILED &&
+			action != CPU_PM_EXIT)
+		return NOTIFY_OK;
+
+	switch (action) {
+	case CPU_PM_ENTER:
+		cpr_pm_disable(cpr_vreg, true);
+		break;
+	case CPU_PM_ENTER_FAILED:
+	case CPU_PM_EXIT:
+		cpr_pm_disable(cpr_vreg, false);
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static int cpr_parse_adj_cpus_init_voltage(struct cpr_regulator *cpr_vreg,
+		struct device *dev)
+{
+	int rc, i, j, k, tuple_count, tuple_match, len, offset;
+	int *temp;
+
+	if (!of_find_property(dev->of_node,
+		   "qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
+		   NULL))
+		return 0;
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/* No matching index to use for voltage adjustment. */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	len = (cpr_vreg->num_adj_cpus + 1) * tuple_count
+		* cpr_vreg->num_corners;
+
+	temp = kzalloc(sizeof(int) * len, GFP_KERNEL);
+	if (!temp) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	cpr_vreg->adj_cpus_open_loop_volt = devm_kzalloc(dev,
+				sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
+				GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus_open_loop_volt) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		rc = -ENOMEM;
+		goto done;
+	}
+
+	cpr_vreg->adj_cpus_open_loop_volt[0] = devm_kzalloc(dev,
+				sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
+				* (cpr_vreg->num_corners + 1),
+				GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus_open_loop_volt[0]) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		rc = -ENOMEM;
+		goto done;
+	}
+	for (i = 1; i <= cpr_vreg->num_adj_cpus; i++)
+		cpr_vreg->adj_cpus_open_loop_volt[i] =
+			cpr_vreg->adj_cpus_open_loop_volt[0] +
+			i * (cpr_vreg->num_corners + 1);
+
+	rc = of_property_read_u32_array(dev->of_node,
+		"qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
+		temp, len);
+	if (rc) {
+		cpr_err(cpr_vreg, "failed to read qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment, rc=%d\n",
+			rc);
+		goto done;
+	}
+
+	cpr_debug(cpr_vreg, "Open loop voltage based on number of online CPUs:\n");
+	offset = tuple_match * cpr_vreg->num_corners *
+			(cpr_vreg->num_adj_cpus + 1);
+
+	for (i = 0; i <= cpr_vreg->num_adj_cpus; i++) {
+		for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) {
+			k = j - 1 + offset;
+
+			cpr_vreg->adj_cpus_open_loop_volt[i][j]
+				= cpr_vreg->open_loop_volt[j] + temp[k];
+			cpr_vreg->adj_cpus_open_loop_volt[i][j]
+			    = DIV_ROUND_UP(cpr_vreg->
+					adj_cpus_open_loop_volt[i][j],
+				cpr_vreg->step_volt) * cpr_vreg->step_volt;
+
+			if (cpr_vreg->adj_cpus_open_loop_volt[i][j]
+					> cpr_vreg->ceiling_volt[j])
+				cpr_vreg->adj_cpus_open_loop_volt[i][j]
+					= cpr_vreg->ceiling_volt[j];
+			if (cpr_vreg->adj_cpus_open_loop_volt[i][j]
+					< cpr_vreg->floor_volt[j])
+				cpr_vreg->adj_cpus_open_loop_volt[i][j]
+					= cpr_vreg->floor_volt[j];
+
+			cpr_debug(cpr_vreg, "cpus=%d, corner=%d, volt=%d\n",
+				i, j, cpr_vreg->adj_cpus_open_loop_volt[i][j]);
+		}
+		offset += cpr_vreg->num_corners;
+	}
+
+	cpr_vreg->adj_cpus_open_loop_volt_as_ceiling
+		= of_property_read_bool(dev->of_node,
+			"qcom,cpr-online-cpu-init-voltage-as-ceiling");
+done:
+	kfree(temp);
+	return rc;
+}
+
+static int cpr_parse_adj_cpus_target_quot(struct cpr_regulator *cpr_vreg,
+		struct device *dev)
+{
+	int rc, i, j, k, tuple_count, tuple_match, len, offset;
+	int *temp;
+
+	if (!of_find_property(dev->of_node,
+		   "qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
+		   NULL))
+		return 0;
+
+	if (cpr_vreg->cpr_fuse_map_count) {
+		if (cpr_vreg->cpr_fuse_map_match == FUSE_MAP_NO_MATCH) {
+			/* No matching index to use for quotient adjustment. */
+			return 0;
+		}
+		tuple_count = cpr_vreg->cpr_fuse_map_count;
+		tuple_match = cpr_vreg->cpr_fuse_map_match;
+	} else {
+		tuple_count = 1;
+		tuple_match = 0;
+	}
+
+	len = (cpr_vreg->num_adj_cpus + 1) * tuple_count
+		* cpr_vreg->num_corners;
+
+	temp = kzalloc(sizeof(int) * len, GFP_KERNEL);
+	if (!temp) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	cpr_vreg->adj_cpus_quot_adjust = devm_kzalloc(dev,
+				sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
+				GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus_quot_adjust) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		rc = -ENOMEM;
+		goto done;
+	}
+
+	cpr_vreg->adj_cpus_quot_adjust[0] = devm_kzalloc(dev,
+				sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
+				* (cpr_vreg->num_corners + 1),
+				GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus_quot_adjust[0]) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		rc = -ENOMEM;
+		goto done;
+	}
+	for (i = 1; i <= cpr_vreg->num_adj_cpus; i++)
+		cpr_vreg->adj_cpus_quot_adjust[i] =
+			cpr_vreg->adj_cpus_quot_adjust[0] +
+			i * (cpr_vreg->num_corners + 1);
+
+
+	rc = of_property_read_u32_array(dev->of_node,
+		"qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
+		temp, len);
+	if (rc) {
+		cpr_err(cpr_vreg, "failed to read qcom,cpr-online-cpu-virtual-corner-quotient-adjustment, rc=%d\n",
+			rc);
+		goto done;
+	}
+
+	cpr_debug(cpr_vreg, "Target quotients based on number of online CPUs:\n");
+	offset = tuple_match * cpr_vreg->num_corners *
+			(cpr_vreg->num_adj_cpus + 1);
+
+	for (i = 0; i <= cpr_vreg->num_adj_cpus; i++) {
+		for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) {
+			k = j - 1 + offset;
+
+			cpr_vreg->adj_cpus_quot_adjust[i][j] =
+					cpr_vreg->quot_adjust[j] - temp[k];
+
+			cpr_debug(cpr_vreg, "cpus=%d, corner=%d, quot=%d\n",
+				i, j,
+				cpr_vreg->cpr_fuse_target_quot[
+							cpr_vreg->corner_map[j]]
+					- cpr_vreg->adj_cpus_quot_adjust[i][j]);
+		}
+		offset += cpr_vreg->num_corners;
+	}
+
+done:
+	kfree(temp);
+	return rc;
+}
+
+static int cpr_init_per_cpu_adjustments(struct cpr_regulator *cpr_vreg,
+		struct device *dev)
+{
+	int rc, i, j;
+
+	if (!of_find_property(dev->of_node,
+		   "qcom,cpr-online-cpu-virtual-corner-init-voltage-adjustment",
+		   NULL)
+	    && !of_find_property(dev->of_node,
+		   "qcom,cpr-online-cpu-virtual-corner-quotient-adjustment",
+		   NULL)) {
+		/* No per-online CPU adjustment needed */
+		return 0;
+	}
+
+	if (!cpr_vreg->num_adj_cpus) {
+		cpr_err(cpr_vreg, "qcom,cpr-cpus property missing\n");
+		return -EINVAL;
+	}
+
+	rc = cpr_parse_adj_cpus_init_voltage(cpr_vreg, dev);
+	if (rc) {
+		cpr_err(cpr_vreg, "cpr_parse_adj_cpus_init_voltage failed: rc =%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = cpr_parse_adj_cpus_target_quot(cpr_vreg, dev);
+	if (rc) {
+		cpr_err(cpr_vreg, "cpr_parse_adj_cpus_target_quot failed: rc =%d\n",
+			rc);
+		return rc;
+	}
+
+	cpr_vreg->adj_cpus_last_volt = devm_kzalloc(dev,
+				sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
+				GFP_KERNEL);
+	cpr_vreg->adj_cpus_save_ctl = devm_kzalloc(dev,
+				sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
+				GFP_KERNEL);
+	cpr_vreg->adj_cpus_save_irq = devm_kzalloc(dev,
+				sizeof(int *) * (cpr_vreg->num_adj_cpus + 1),
+				GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus_last_volt || !cpr_vreg->adj_cpus_save_ctl ||
+		!cpr_vreg->adj_cpus_save_irq) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		return -ENOMEM;
+	}
+
+	cpr_vreg->adj_cpus_last_volt[0] = devm_kzalloc(dev,
+				sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
+				* (cpr_vreg->num_corners + 1),
+				GFP_KERNEL);
+	cpr_vreg->adj_cpus_save_ctl[0] = devm_kzalloc(dev,
+				sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
+				* (cpr_vreg->num_corners + 1),
+				GFP_KERNEL);
+	cpr_vreg->adj_cpus_save_irq[0] = devm_kzalloc(dev,
+				sizeof(int) * (cpr_vreg->num_adj_cpus + 1)
+				* (cpr_vreg->num_corners + 1),
+				GFP_KERNEL);
+	if (!cpr_vreg->adj_cpus_last_volt[0] ||
+		!cpr_vreg->adj_cpus_save_ctl[0] ||
+		!cpr_vreg->adj_cpus_save_irq[0]) {
+		cpr_err(cpr_vreg, "Could not allocate memory\n");
+		return -ENOMEM;
+	}
+	for (i = 1; i <= cpr_vreg->num_adj_cpus; i++) {
+		j = i * (cpr_vreg->num_corners + 1);
+		cpr_vreg->adj_cpus_last_volt[i] =
+			cpr_vreg->adj_cpus_last_volt[0] + j;
+		cpr_vreg->adj_cpus_save_ctl[i] =
+			cpr_vreg->adj_cpus_save_ctl[0] + j;
+		cpr_vreg->adj_cpus_save_irq[i] =
+			cpr_vreg->adj_cpus_save_irq[0] + j;
+	}
+
+
+	for (i = 0; i <= cpr_vreg->num_adj_cpus; i++) {
+		for (j = CPR_CORNER_MIN; j <= cpr_vreg->num_corners; j++) {
+
+			cpr_vreg->adj_cpus_save_ctl[i][j] =
+				cpr_vreg->save_ctl[j];
+			cpr_vreg->adj_cpus_save_irq[i][j] =
+				cpr_vreg->save_irq[j];
+
+			cpr_vreg->adj_cpus_last_volt[i][j]
+				= cpr_vreg->adj_cpus_open_loop_volt
+				? cpr_vreg->adj_cpus_open_loop_volt[i][j]
+					: cpr_vreg->open_loop_volt[j];
+		}
+	}
+
+	cpr_regulator_set_online_cpus(cpr_vreg);
+	cpr_debug(cpr_vreg, "%d cpus online\n", cpr_vreg->online_cpus);
+
+	devm_kfree(dev, cpr_vreg->last_volt);
+	devm_kfree(dev, cpr_vreg->save_ctl);
+	devm_kfree(dev, cpr_vreg->save_irq);
+	if (cpr_vreg->adj_cpus_quot_adjust)
+		devm_kfree(dev, cpr_vreg->quot_adjust);
+	if (cpr_vreg->adj_cpus_open_loop_volt)
+		devm_kfree(dev, cpr_vreg->open_loop_volt);
+	if (cpr_vreg->adj_cpus_open_loop_volt_as_ceiling)
+		devm_kfree(dev, cpr_vreg->ceiling_volt);
+
+	cpr_regulator_switch_adj_cpus(cpr_vreg);
+
+	cpr_vreg->skip_voltage_change_during_suspend
+			= of_property_read_bool(dev->of_node,
+				"qcom,cpr-skip-voltage-change-during-suspend");
+
+	cpr_vreg->cpu_notifier.notifier_call = cpr_regulator_cpu_callback;
+	register_hotcpu_notifier(&cpr_vreg->cpu_notifier);
+
+	return rc;
+}
+
+static int cpr_init_pm_notification(struct cpr_regulator *cpr_vreg)
+{
+	int rc;
+
+	/* enabled only for single-core designs */
+	if (cpr_vreg->num_adj_cpus != 1) {
+		pr_warn("qcom,cpr-cpus not defined or invalid %d\n",
+					cpr_vreg->num_adj_cpus);
+		return 0;
+	}
+
+	cpr_vreg->pm_notifier.notifier_call = cpr_pm_callback;
+	rc = cpu_pm_register_notifier(&cpr_vreg->pm_notifier);
+	if (rc)
+		cpr_err(cpr_vreg, "Unable to register pm notifier rc=%d\n", rc);
+
+	return rc;
+}
+
+static int cpr_rpm_apc_init(struct platform_device *pdev,
+			       struct cpr_regulator *cpr_vreg)
+{
+	int rc, len = 0;
+	struct device_node *of_node = pdev->dev.of_node;
+
+	if (!of_find_property(of_node, "rpm-apc-supply", NULL))
+		return 0;
+
+	cpr_vreg->rpm_apc_vreg = devm_regulator_get(&pdev->dev, "rpm-apc");
+	if (IS_ERR_OR_NULL(cpr_vreg->rpm_apc_vreg)) {
+		rc = PTR_RET(cpr_vreg->rpm_apc_vreg);
+		if (rc != -EPROBE_DEFER)
+			cpr_err(cpr_vreg, "devm_regulator_get: rpm-apc: rc=%d\n",
+					rc);
+		return rc;
+	}
+
+	if (!of_find_property(of_node, "qcom,rpm-apc-corner-map", &len)) {
+		cpr_err(cpr_vreg,
+			"qcom,rpm-apc-corner-map missing:\n");
+		return -EINVAL;
+	}
+	if (len != cpr_vreg->num_corners * sizeof(u32)) {
+		cpr_err(cpr_vreg,
+			"qcom,rpm-apc-corner-map length=%d is invalid: required:%d\n",
+			len, cpr_vreg->num_corners);
+		return -EINVAL;
+	}
+
+	cpr_vreg->rpm_apc_corner_map = devm_kzalloc(&pdev->dev,
+		(cpr_vreg->num_corners + 1) *
+		sizeof(*cpr_vreg->rpm_apc_corner_map), GFP_KERNEL);
+	if (!cpr_vreg->rpm_apc_corner_map) {
+		cpr_err(cpr_vreg, "Can't allocate memory for cpr_vreg->rpm_apc_corner_map\n");
+			return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,rpm-apc-corner-map",
+		&cpr_vreg->rpm_apc_corner_map[1], cpr_vreg->num_corners);
+	if (rc)
+		cpr_err(cpr_vreg, "read qcom,rpm-apc-corner-map failed, rc = %d\n",
+				rc);
+
+	return rc;
+}
+
+static int cpr_vsens_init(struct platform_device *pdev,
+			       struct cpr_regulator *cpr_vreg)
+{
+	int rc = 0, len = 0;
+	struct device_node *of_node = pdev->dev.of_node;
+
+	if (of_find_property(of_node, "vdd-vsens-voltage-supply", NULL)) {
+		cpr_vreg->vdd_vsens_voltage = devm_regulator_get(&pdev->dev,
+							"vdd-vsens-voltage");
+		if (IS_ERR_OR_NULL(cpr_vreg->vdd_vsens_voltage)) {
+			rc = PTR_ERR(cpr_vreg->vdd_vsens_voltage);
+			cpr_vreg->vdd_vsens_voltage = NULL;
+			if (rc == -EPROBE_DEFER)
+				return rc;
+			/* device not found */
+			cpr_debug(cpr_vreg, "regulator_get: vdd-vsens-voltage: rc=%d\n",
+					rc);
+			return 0;
+		}
+	}
+
+	if (of_find_property(of_node, "vdd-vsens-corner-supply", NULL)) {
+		cpr_vreg->vdd_vsens_corner = devm_regulator_get(&pdev->dev,
+							"vdd-vsens-corner");
+		if (IS_ERR_OR_NULL(cpr_vreg->vdd_vsens_corner)) {
+			rc = PTR_ERR(cpr_vreg->vdd_vsens_corner);
+			cpr_vreg->vdd_vsens_corner = NULL;
+			if (rc == -EPROBE_DEFER)
+				return rc;
+			/* device not found */
+			cpr_debug(cpr_vreg, "regulator_get: vdd-vsens-corner: rc=%d\n",
+					rc);
+			return 0;
+		}
+
+		if (!of_find_property(of_node, "qcom,vsens-corner-map", &len)) {
+			cpr_err(cpr_vreg, "qcom,vsens-corner-map missing\n");
+			return -EINVAL;
+		}
+
+		if (len != cpr_vreg->num_fuse_corners * sizeof(u32)) {
+			cpr_err(cpr_vreg, "qcom,vsens-corner-map length=%d is invalid: required:%d\n",
+				len, cpr_vreg->num_fuse_corners);
+			return -EINVAL;
+		}
+
+		cpr_vreg->vsens_corner_map = devm_kcalloc(&pdev->dev,
+					(cpr_vreg->num_fuse_corners + 1),
+			sizeof(*cpr_vreg->vsens_corner_map), GFP_KERNEL);
+		if (!cpr_vreg->vsens_corner_map)
+			return -ENOMEM;
+
+		rc = of_property_read_u32_array(of_node,
+					"qcom,vsens-corner-map",
+					&cpr_vreg->vsens_corner_map[1],
+					cpr_vreg->num_fuse_corners);
+		if (rc)
+			cpr_err(cpr_vreg, "read qcom,vsens-corner-map failed, rc = %d\n",
+				rc);
+	}
+
+	return rc;
+}
+
+static int cpr_disable_on_temp(struct cpr_regulator *cpr_vreg, bool disable)
+{
+	int rc = 0;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	if (cpr_vreg->cpr_fuse_disable ||
+		(cpr_vreg->cpr_thermal_disable == disable))
+		goto out;
+
+	cpr_vreg->cpr_thermal_disable = disable;
+
+	if (cpr_vreg->enable && cpr_vreg->corner) {
+		if (disable) {
+			cpr_debug(cpr_vreg, "Disabling CPR - below temperature threshold [%d]\n",
+					cpr_vreg->cpr_disable_temp_threshold);
+			/* disable CPR and force open-loop */
+			cpr_ctl_disable(cpr_vreg);
+			rc = cpr_regulator_set_voltage(cpr_vreg->rdev,
+						cpr_vreg->corner, false);
+			if (rc < 0)
+				cpr_err(cpr_vreg, "Failed to set voltage, rc=%d\n",
+						rc);
+		} else {
+			/* enable CPR */
+			cpr_debug(cpr_vreg, "Enabling CPR - above temperature thresold [%d]\n",
+					cpr_vreg->cpr_enable_temp_threshold);
+			rc = cpr_regulator_set_voltage(cpr_vreg->rdev,
+						cpr_vreg->corner, true);
+			if (rc < 0)
+				cpr_err(cpr_vreg, "Failed to set voltage, rc=%d\n",
+						rc);
+		}
+	}
+out:
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+	return rc;
+}
+
+static void tsens_threshold_notify(struct therm_threshold *tsens_cb_data)
+{
+	struct threshold_info *info = tsens_cb_data->parent;
+	struct cpr_regulator *cpr_vreg = container_of(info,
+			struct cpr_regulator, tsens_threshold_config);
+	int rc = 0;
+
+	cpr_debug(cpr_vreg, "Triggered tsens-notification trip_type=%d for thermal_zone_id=%d\n",
+		tsens_cb_data->trip_triggered, tsens_cb_data->sensor_id);
+
+	switch (tsens_cb_data->trip_triggered) {
+	case THERMAL_TRIP_CONFIGURABLE_HI:
+		rc = cpr_disable_on_temp(cpr_vreg, false);
+		if (rc < 0)
+			cpr_err(cpr_vreg, "Failed to enable CPR, rc=%d\n", rc);
+		break;
+	case THERMAL_TRIP_CONFIGURABLE_LOW:
+		rc = cpr_disable_on_temp(cpr_vreg, true);
+		if (rc < 0)
+			cpr_err(cpr_vreg, "Failed to disable CPR, rc=%d\n", rc);
+		break;
+	default:
+		cpr_debug(cpr_vreg, "trip-type %d not supported\n",
+				tsens_cb_data->trip_triggered);
+		break;
+	}
+
+	if (tsens_cb_data->cur_state != tsens_cb_data->trip_triggered) {
+		rc = sensor_mgr_set_threshold(tsens_cb_data->sensor_id,
+						tsens_cb_data->threshold);
+		if (rc < 0)
+			cpr_err(cpr_vreg,
+			"Failed to set temp. threshold, rc=%d\n", rc);
+		else
+			tsens_cb_data->cur_state =
+				tsens_cb_data->trip_triggered;
+	}
+}
+
+static int cpr_check_tsens(struct cpr_regulator *cpr_vreg)
+{
+	int rc = 0;
+	struct tsens_device tsens_dev;
+	unsigned long temp = 0;
+	bool disable;
+
+	if (tsens_is_ready() > 0) {
+		tsens_dev.sensor_num = cpr_vreg->tsens_id;
+		rc = tsens_get_temp(&tsens_dev, &temp);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "Faled to read tsens, rc=%d\n", rc);
+			return rc;
+		}
+
+		disable = (int) temp <= cpr_vreg->cpr_disable_temp_threshold;
+		rc = cpr_disable_on_temp(cpr_vreg, disable);
+		if (rc)
+			cpr_err(cpr_vreg, "Failed to %s CPR, rc=%d\n",
+					disable ? "disable" : "enable", rc);
+	}
+
+	return rc;
+}
+
+static int cpr_thermal_init(struct cpr_regulator *cpr_vreg)
+{
+	int rc;
+	struct device_node *of_node = cpr_vreg->dev->of_node;
+
+	if (!of_find_property(of_node, "qcom,cpr-thermal-sensor-id", NULL))
+		return 0;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-thermal-sensor-id",
+			  &cpr_vreg->tsens_id, rc);
+	if (rc < 0)
+		return rc;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-disable-temp-threshold",
+			  &cpr_vreg->cpr_disable_temp_threshold, rc);
+	if (rc < 0)
+		return rc;
+
+	CPR_PROP_READ_U32(cpr_vreg, of_node, "cpr-enable-temp-threshold",
+			  &cpr_vreg->cpr_enable_temp_threshold, rc);
+	if (rc < 0)
+		return rc;
+
+	if (cpr_vreg->cpr_disable_temp_threshold >=
+				cpr_vreg->cpr_enable_temp_threshold) {
+		cpr_err(cpr_vreg, "Invalid temperature threshold cpr_disable_temp[%d] >= cpr_enable_temp[%d]\n",
+				cpr_vreg->cpr_disable_temp_threshold,
+				cpr_vreg->cpr_enable_temp_threshold);
+		return -EINVAL;
+	}
+
+	cpr_vreg->cpr_disable_on_temperature = true;
+
+	return 0;
+}
+
+static int cpr_init_cpr(struct platform_device *pdev,
+			       struct cpr_regulator *cpr_vreg)
+{
+	struct resource *res;
+	int rc = 0;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr_clk");
+	if (res && res->start)
+		cpr_vreg->rbcpr_clk_addr = res->start;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rbcpr");
+	if (!res || !res->start) {
+		cpr_err(cpr_vreg, "missing rbcpr address: res=%p\n", res);
+		return -EINVAL;
+	}
+	cpr_vreg->rbcpr_base = devm_ioremap(&pdev->dev, res->start,
+					    resource_size(res));
+
+	/* Init CPR configuration parameters */
+	rc = cpr_init_cpr_parameters(pdev, cpr_vreg);
+	if (rc)
+		return rc;
+
+	rc = cpr_init_cpr_efuse(pdev, cpr_vreg);
+	if (rc)
+		return rc;
+
+	/* Load per corner ceiling and floor voltages if they exist. */
+	rc = cpr_init_ceiling_floor_override_voltages(cpr_vreg, &pdev->dev);
+	if (rc)
+		return rc;
+
+	/*
+	 * Limit open loop voltages based upon per corner ceiling and floor
+	 * voltages.
+	 */
+	rc = cpr_limit_open_loop_voltage(cpr_vreg);
+	if (rc)
+		return rc;
+
+	/*
+	 * Fill the OPP table for this device with virtual voltage corner to
+	 * open-loop voltage pairs.
+	 */
+	rc = cpr_populate_opp_table(cpr_vreg, &pdev->dev);
+	if (rc)
+		return rc;
+
+	/* Reduce the ceiling voltage if allowed. */
+	rc = cpr_reduce_ceiling_voltage(cpr_vreg, &pdev->dev);
+	if (rc)
+		return rc;
+
+	/* Load CPR floor to ceiling range if exist. */
+	rc = cpr_init_floor_to_ceiling_range(cpr_vreg, &pdev->dev);
+	if (rc)
+		return rc;
+
+	/* Init all voltage set points of APC regulator for CPR */
+	rc = cpr_init_cpr_voltages(cpr_vreg, &pdev->dev);
+	if (rc)
+		return rc;
+
+	/* Get and Init interrupt */
+	cpr_vreg->cpr_irq = platform_get_irq(pdev, 0);
+	if (!cpr_vreg->cpr_irq) {
+		cpr_err(cpr_vreg, "missing CPR IRQ\n");
+		return -EINVAL;
+	}
+
+	/* Configure CPR HW but keep it disabled */
+	rc = cpr_config(cpr_vreg, &pdev->dev);
+	if (rc)
+		return rc;
+
+	rc = request_threaded_irq(cpr_vreg->cpr_irq, NULL, cpr_irq_handler,
+				  IRQF_ONESHOT | IRQF_TRIGGER_RISING, "cpr",
+				  cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "CPR: request irq failed for IRQ %d\n",
+				cpr_vreg->cpr_irq);
+		return rc;
+	}
+
+	return 0;
+}
+
+/*
+ * Create a set of virtual fuse rows if optional device tree properties are
+ * present.
+ */
+static int cpr_remap_efuse_data(struct platform_device *pdev,
+				 struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	struct property *prop;
+	u64 fuse_param;
+	u32 *temp;
+	int size, rc, i, bits, in_row, in_bit, out_row, out_bit;
+
+	prop = of_find_property(of_node, "qcom,fuse-remap-source", NULL);
+	if (!prop) {
+		/* No fuse remapping needed. */
+		return 0;
+	}
+
+	size = prop->length / sizeof(u32);
+	if (size == 0 || size % 4) {
+		cpr_err(cpr_vreg, "qcom,fuse-remap-source has invalid size=%d\n",
+			size);
+		return -EINVAL;
+	}
+	size /= 4;
+
+	rc = of_property_read_u32(of_node, "qcom,fuse-remap-base-row",
+				&cpr_vreg->remapped_row_base);
+	if (rc) {
+		cpr_err(cpr_vreg, "could not read qcom,fuse-remap-base-row, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	temp = kzalloc(sizeof(*temp) * size * 4, GFP_KERNEL);
+	if (!temp) {
+		cpr_err(cpr_vreg, "temp memory allocation failed\n");
+		return -ENOMEM;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,fuse-remap-source", temp,
+					size * 4);
+	if (rc) {
+		cpr_err(cpr_vreg, "could not read qcom,fuse-remap-source, rc=%d\n",
+			rc);
+		goto done;
+	}
+
+	/*
+	 * Format of tuples in qcom,fuse-remap-source property:
+	 * <row bit-offset bit-count fuse-read-method>
+	 */
+	for (i = 0, bits = 0; i < size; i++)
+		bits += temp[i * 4 + 2];
+
+	cpr_vreg->num_remapped_rows = DIV_ROUND_UP(bits, 64);
+	cpr_vreg->remapped_row = devm_kzalloc(&pdev->dev,
+		sizeof(*cpr_vreg->remapped_row) * cpr_vreg->num_remapped_rows,
+		GFP_KERNEL);
+	if (!cpr_vreg->remapped_row) {
+		cpr_err(cpr_vreg, "remapped_row memory allocation failed\n");
+		rc = -ENOMEM;
+		goto done;
+	}
+
+	for (i = 0, out_row = 0, out_bit = 0; i < size; i++) {
+		in_row = temp[i * 4];
+		in_bit = temp[i * 4 + 1];
+		bits = temp[i * 4 + 2];
+
+		while (bits > 64) {
+			fuse_param = cpr_read_efuse_param(cpr_vreg, in_row,
+					in_bit, 64, temp[i * 4 + 3]);
+
+			cpr_vreg->remapped_row[out_row++]
+				|= fuse_param << out_bit;
+			if (out_bit > 0)
+				cpr_vreg->remapped_row[out_row]
+					|= fuse_param >> (64 - out_bit);
+
+			bits -= 64;
+			in_bit += 64;
+		}
+
+		fuse_param = cpr_read_efuse_param(cpr_vreg, in_row, in_bit,
+						bits, temp[i * 4 + 3]);
+
+		cpr_vreg->remapped_row[out_row] |= fuse_param << out_bit;
+		if (bits < 64 - out_bit) {
+			out_bit += bits;
+		} else {
+			out_row++;
+			if (out_bit > 0)
+				cpr_vreg->remapped_row[out_row]
+					|= fuse_param >> (64 - out_bit);
+			out_bit = bits - (64 - out_bit);
+		}
+	}
+
+done:
+	kfree(temp);
+	return rc;
+}
+
+static int cpr_efuse_init(struct platform_device *pdev,
+				 struct cpr_regulator *cpr_vreg)
+{
+	struct resource *res;
+	int len;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "efuse_addr");
+	if (!res || !res->start) {
+		cpr_err(cpr_vreg, "efuse_addr missing: res=%p\n", res);
+		return -EINVAL;
+	}
+
+	cpr_vreg->efuse_addr = res->start;
+	len = res->end - res->start + 1;
+
+	cpr_info(cpr_vreg, "efuse_addr = %pa (len=0x%x)\n", &res->start, len);
+
+	cpr_vreg->efuse_base = ioremap(cpr_vreg->efuse_addr, len);
+	if (!cpr_vreg->efuse_base) {
+		cpr_err(cpr_vreg, "Unable to map efuse_addr %pa\n",
+				&cpr_vreg->efuse_addr);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void cpr_efuse_free(struct cpr_regulator *cpr_vreg)
+{
+	iounmap(cpr_vreg->efuse_base);
+}
+
+static void cpr_parse_cond_min_volt_fuse(struct cpr_regulator *cpr_vreg,
+						struct device_node *of_node)
+{
+	int rc;
+	u32 fuse_sel[5];
+	/*
+	 * Restrict all pvs corner voltages to a minimum value of
+	 * qcom,cpr-cond-min-voltage if the fuse defined in
+	 * qcom,cpr-fuse-cond-min-volt-sel does not read back with
+	 * the expected value.
+	 */
+	rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-fuse-cond-min-volt-sel", fuse_sel, 5);
+	if (!rc) {
+		if (!cpr_fuse_is_setting_expected(cpr_vreg, fuse_sel))
+			cpr_vreg->flags |= FLAGS_SET_MIN_VOLTAGE;
+	}
+}
+
+static void cpr_parse_speed_bin_fuse(struct cpr_regulator *cpr_vreg,
+				struct device_node *of_node)
+{
+	int rc;
+	u64 fuse_bits;
+	u32 fuse_sel[4];
+	u32 speed_bits;
+
+	rc = of_property_read_u32_array(of_node,
+			"qcom,speed-bin-fuse-sel", fuse_sel, 4);
+
+	if (!rc) {
+		fuse_bits = cpr_read_efuse_row(cpr_vreg,
+				fuse_sel[0], fuse_sel[3]);
+		speed_bits = (fuse_bits >> fuse_sel[1]) &
+			((1 << fuse_sel[2]) - 1);
+		cpr_info(cpr_vreg, "[row: %d]: 0x%llx, speed_bits = %d\n",
+				fuse_sel[0], fuse_bits, speed_bits);
+		cpr_vreg->speed_bin = speed_bits;
+	} else {
+		cpr_vreg->speed_bin = SPEED_BIN_NONE;
+	}
+}
+
+static int cpr_voltage_uplift_enable_check(struct cpr_regulator *cpr_vreg,
+					struct device_node *of_node)
+{
+	int rc;
+	u32 fuse_sel[5];
+	u32 uplift_speed_bin;
+
+	rc = of_property_read_u32_array(of_node,
+			"qcom,cpr-fuse-uplift-sel", fuse_sel, 5);
+	if (!rc) {
+		rc = of_property_read_u32(of_node,
+				"qcom,cpr-uplift-speed-bin",
+				&uplift_speed_bin);
+		if (rc < 0) {
+			cpr_err(cpr_vreg,
+				"qcom,cpr-uplift-speed-bin missing\n");
+			return rc;
+		}
+		if (cpr_fuse_is_setting_expected(cpr_vreg, fuse_sel)
+			&& (uplift_speed_bin == cpr_vreg->speed_bin)
+			&& !(cpr_vreg->flags & FLAGS_SET_MIN_VOLTAGE)) {
+			cpr_vreg->flags |= FLAGS_UPLIFT_QUOT_VOLT;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Read in the number of fuse corners and then allocate memory for arrays that
+ * are sized based upon the number of fuse corners.
+ */
+static int cpr_fuse_corner_array_alloc(struct device *dev,
+					struct cpr_regulator *cpr_vreg)
+{
+	int rc;
+	size_t len;
+
+	rc = of_property_read_u32(dev->of_node, "qcom,cpr-fuse-corners",
+				&cpr_vreg->num_fuse_corners);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "qcom,cpr-fuse-corners missing: rc=%d\n", rc);
+		return rc;
+	}
+
+	if (cpr_vreg->num_fuse_corners < CPR_FUSE_CORNER_MIN
+	    || cpr_vreg->num_fuse_corners > CPR_FUSE_CORNER_LIMIT) {
+		cpr_err(cpr_vreg, "corner count=%d is invalid\n",
+			cpr_vreg->num_fuse_corners);
+		return -EINVAL;
+	}
+
+	/*
+	 * The arrays sized based on the fuse corner count ignore element 0
+	 * in order to simplify indexing throughout the driver since min_uV = 0
+	 * cannot be passed into a set_voltage() callback.
+	 */
+	len = cpr_vreg->num_fuse_corners + 1;
+
+	cpr_vreg->pvs_corner_v = devm_kzalloc(dev,
+			len * sizeof(*cpr_vreg->pvs_corner_v), GFP_KERNEL);
+	cpr_vreg->cpr_fuse_target_quot = devm_kzalloc(dev,
+		len * sizeof(*cpr_vreg->cpr_fuse_target_quot), GFP_KERNEL);
+	cpr_vreg->cpr_fuse_ro_sel = devm_kzalloc(dev,
+			len * sizeof(*cpr_vreg->cpr_fuse_ro_sel), GFP_KERNEL);
+	cpr_vreg->fuse_ceiling_volt = devm_kzalloc(dev,
+		len * (sizeof(*cpr_vreg->fuse_ceiling_volt)), GFP_KERNEL);
+	cpr_vreg->fuse_floor_volt = devm_kzalloc(dev,
+		len * (sizeof(*cpr_vreg->fuse_floor_volt)), GFP_KERNEL);
+	cpr_vreg->step_quotient = devm_kzalloc(dev,
+		len * sizeof(*cpr_vreg->step_quotient), GFP_KERNEL);
+
+	if (cpr_vreg->pvs_corner_v == NULL || cpr_vreg->cpr_fuse_ro_sel == NULL
+	    || cpr_vreg->fuse_ceiling_volt == NULL
+	    || cpr_vreg->fuse_floor_volt == NULL
+	    || cpr_vreg->cpr_fuse_target_quot == NULL
+	    || cpr_vreg->step_quotient == NULL) {
+		cpr_err(cpr_vreg, "Could not allocate memory for CPR arrays\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int cpr_voltage_plan_init(struct platform_device *pdev,
+					struct cpr_regulator *cpr_vreg)
+{
+	struct device_node *of_node = pdev->dev.of_node;
+	int rc, i;
+	u32 min_uv = 0;
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-voltage-ceiling",
+		&cpr_vreg->fuse_ceiling_volt[CPR_FUSE_CORNER_MIN],
+		cpr_vreg->num_fuse_corners);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "cpr-voltage-ceiling missing: rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32_array(of_node, "qcom,cpr-voltage-floor",
+		&cpr_vreg->fuse_floor_volt[CPR_FUSE_CORNER_MIN],
+		cpr_vreg->num_fuse_corners);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "cpr-voltage-floor missing: rc=%d\n", rc);
+		return rc;
+	}
+
+	cpr_parse_cond_min_volt_fuse(cpr_vreg, of_node);
+	rc = cpr_voltage_uplift_enable_check(cpr_vreg, of_node);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "voltage uplift enable check failed, %d\n",
+			rc);
+		return rc;
+	}
+	if (cpr_vreg->flags & FLAGS_SET_MIN_VOLTAGE) {
+		of_property_read_u32(of_node, "qcom,cpr-cond-min-voltage",
+					&min_uv);
+		for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners;
+		     i++)
+			if (cpr_vreg->fuse_ceiling_volt[i] < min_uv) {
+				cpr_vreg->fuse_ceiling_volt[i] = min_uv;
+				cpr_vreg->fuse_floor_volt[i] = min_uv;
+			} else if (cpr_vreg->fuse_floor_volt[i] < min_uv) {
+				cpr_vreg->fuse_floor_volt[i] = min_uv;
+			}
+	}
+
+	return 0;
+}
+
+static int cpr_mem_acc_init(struct platform_device *pdev,
+				struct cpr_regulator *cpr_vreg)
+{
+	int rc, size;
+	struct property *prop;
+	char *corner_map_str;
+
+	if (of_find_property(pdev->dev.of_node, "mem-acc-supply", NULL)) {
+		cpr_vreg->mem_acc_vreg = devm_regulator_get(&pdev->dev,
+							"mem-acc");
+		if (IS_ERR_OR_NULL(cpr_vreg->mem_acc_vreg)) {
+			rc = PTR_RET(cpr_vreg->mem_acc_vreg);
+			if (rc != -EPROBE_DEFER)
+				cpr_err(cpr_vreg,
+					"devm_regulator_get: mem-acc: rc=%d\n",
+					rc);
+			return rc;
+		}
+	}
+
+	corner_map_str = "qcom,mem-acc-corner-map";
+	prop = of_find_property(pdev->dev.of_node, corner_map_str, NULL);
+	if (!prop) {
+		corner_map_str = "qcom,cpr-corner-map";
+		prop = of_find_property(pdev->dev.of_node, corner_map_str,
+					NULL);
+		if (!prop) {
+			cpr_err(cpr_vreg, "qcom,cpr-corner-map missing\n");
+			return -EINVAL;
+		}
+	}
+
+	size = prop->length / sizeof(u32);
+	cpr_vreg->mem_acc_corner_map = devm_kzalloc(&pdev->dev,
+					sizeof(int) * (size + 1),
+					GFP_KERNEL);
+
+	rc = of_property_read_u32_array(pdev->dev.of_node, corner_map_str,
+			&cpr_vreg->mem_acc_corner_map[CPR_FUSE_CORNER_MIN],
+			size);
+	if (rc) {
+		cpr_err(cpr_vreg, "%s missing, rc = %d\n", corner_map_str, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+
+static int cpr_enable_set(void *data, u64 val)
+{
+	struct cpr_regulator *cpr_vreg = data;
+	bool old_cpr_enable;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	old_cpr_enable = cpr_vreg->enable;
+	cpr_vreg->enable = val;
+
+	if (old_cpr_enable == cpr_vreg->enable)
+		goto _exit;
+
+	if (cpr_vreg->enable && cpr_vreg->cpr_fuse_disable) {
+		cpr_info(cpr_vreg,
+			"CPR permanently disabled due to fuse values\n");
+		cpr_vreg->enable = false;
+		goto _exit;
+	}
+
+	cpr_debug(cpr_vreg, "%s CPR [corner=%d, fuse_corner=%d]\n",
+		cpr_vreg->enable ? "enabling" : "disabling",
+		cpr_vreg->corner, cpr_vreg->corner_map[cpr_vreg->corner]);
+
+	if (cpr_vreg->corner) {
+		if (cpr_vreg->enable) {
+			cpr_ctl_disable(cpr_vreg);
+			cpr_irq_clr(cpr_vreg);
+			cpr_corner_restore(cpr_vreg, cpr_vreg->corner);
+			cpr_ctl_enable(cpr_vreg, cpr_vreg->corner);
+		} else {
+			cpr_ctl_disable(cpr_vreg);
+			cpr_irq_set(cpr_vreg, 0);
+		}
+	}
+
+_exit:
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	return 0;
+}
+
+static int cpr_enable_get(void *data, u64 *val)
+{
+	struct cpr_regulator *cpr_vreg = data;
+
+	*val = cpr_vreg->enable;
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(cpr_enable_fops, cpr_enable_get, cpr_enable_set,
+			"%llu\n");
+
+static int cpr_get_cpr_ceiling(void *data, u64 *val)
+{
+	struct cpr_regulator *cpr_vreg = data;
+
+	*val = cpr_vreg->ceiling_volt[cpr_vreg->corner];
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(cpr_ceiling_fops, cpr_get_cpr_ceiling, NULL,
+			"%llu\n");
+
+static int cpr_get_cpr_floor(void *data, u64 *val)
+{
+	struct cpr_regulator *cpr_vreg = data;
+
+	*val = cpr_vreg->floor_volt[cpr_vreg->corner];
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(cpr_floor_fops, cpr_get_cpr_floor, NULL,
+			"%llu\n");
+
+static int cpr_get_cpr_max_ceiling(void *data, u64 *val)
+{
+	struct cpr_regulator *cpr_vreg = data;
+
+	*val = cpr_vreg->cpr_max_ceiling[cpr_vreg->corner];
+
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(cpr_max_ceiling_fops, cpr_get_cpr_max_ceiling, NULL,
+			"%llu\n");
+
+static int cpr_debug_info_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+
+	return 0;
+}
+
+static ssize_t cpr_debug_info_read(struct file *file, char __user *buff,
+				size_t count, loff_t *ppos)
+{
+	struct cpr_regulator *cpr_vreg = file->private_data;
+	char *debugfs_buf;
+	ssize_t len, ret = 0;
+	u32 gcnt, ro_sel, ctl, irq_status, reg, error_steps;
+	u32 step_dn, step_up, error, error_lt0, busy;
+	int fuse_corner;
+
+	debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!debugfs_buf)
+		return -ENOMEM;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	fuse_corner = cpr_vreg->corner_map[cpr_vreg->corner];
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+		"corner = %d, current_volt = %d uV\n",
+		cpr_vreg->corner, cpr_vreg->last_volt[cpr_vreg->corner]);
+	ret += len;
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"fuse_corner = %d, current_volt = %d uV\n",
+			fuse_corner, cpr_vreg->last_volt[cpr_vreg->corner]);
+	ret += len;
+
+	ro_sel = cpr_vreg->cpr_fuse_ro_sel[fuse_corner];
+	gcnt = cpr_read(cpr_vreg, REG_RBCPR_GCNT_TARGET(ro_sel));
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"rbcpr_gcnt_target (%u) = 0x%02X\n", ro_sel, gcnt);
+	ret += len;
+
+	ctl = cpr_read(cpr_vreg, REG_RBCPR_CTL);
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"rbcpr_ctl = 0x%02X\n", ctl);
+	ret += len;
+
+	irq_status = cpr_read(cpr_vreg, REG_RBIF_IRQ_STATUS);
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"rbcpr_irq_status = 0x%02X\n", irq_status);
+	ret += len;
+
+	reg = cpr_read(cpr_vreg, REG_RBCPR_RESULT_0);
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"rbcpr_result_0 = 0x%02X\n", reg);
+	ret += len;
+
+	step_dn = reg & 0x01;
+	step_up = (reg >> RBCPR_RESULT0_STEP_UP_SHIFT) & 0x01;
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"  [step_dn = %u", step_dn);
+	ret += len;
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			", step_up = %u", step_up);
+	ret += len;
+
+	error_steps = (reg >> RBCPR_RESULT0_ERROR_STEPS_SHIFT)
+				& RBCPR_RESULT0_ERROR_STEPS_MASK;
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			", error_steps = %u", error_steps);
+	ret += len;
+
+	error = (reg >> RBCPR_RESULT0_ERROR_SHIFT) & RBCPR_RESULT0_ERROR_MASK;
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			", error = %u", error);
+	ret += len;
+
+	error_lt0 = (reg >> RBCPR_RESULT0_ERROR_LT0_SHIFT) & 0x01;
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			", error_lt_0 = %u", error_lt0);
+	ret += len;
+
+	busy = (reg >> RBCPR_RESULT0_BUSY_SHIFT) & 0x01;
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			", busy = %u]\n", busy);
+	ret += len;
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret);
+	kfree(debugfs_buf);
+	return ret;
+}
+
+static const struct file_operations cpr_debug_info_fops = {
+	.open = cpr_debug_info_open,
+	.read = cpr_debug_info_read,
+};
+
+static int cpr_aging_debug_info_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+
+	return 0;
+}
+
+static ssize_t cpr_aging_debug_info_read(struct file *file, char __user *buff,
+				size_t count, loff_t *ppos)
+{
+	struct cpr_regulator *cpr_vreg = file->private_data;
+	struct cpr_aging_info *aging_info = cpr_vreg->aging_info;
+	char *debugfs_buf;
+	ssize_t len, ret = 0;
+	int i;
+
+	debugfs_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!debugfs_buf)
+		return -ENOMEM;
+
+	mutex_lock(&cpr_vreg->cpr_mutex);
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"aging_adj_volt = [");
+	ret += len;
+
+	for (i = CPR_FUSE_CORNER_MIN; i <= cpr_vreg->num_fuse_corners; i++) {
+		len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+				" %d", aging_info->voltage_adjust[i]);
+		ret += len;
+	}
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			" ]uV\n");
+	ret += len;
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"aging_measurement_done = %s\n",
+			aging_info->cpr_aging_done ? "true" : "false");
+	ret += len;
+
+	len = snprintf(debugfs_buf + ret, PAGE_SIZE - ret,
+			"aging_measurement_error = %s\n",
+			aging_info->cpr_aging_error ? "true" : "false");
+	ret += len;
+
+	mutex_unlock(&cpr_vreg->cpr_mutex);
+
+	ret = simple_read_from_buffer(buff, count, ppos, debugfs_buf, ret);
+	kfree(debugfs_buf);
+	return ret;
+}
+
+static const struct file_operations cpr_aging_debug_info_fops = {
+	.open = cpr_aging_debug_info_open,
+	.read = cpr_aging_debug_info_read,
+};
+
+static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg)
+{
+	struct dentry *temp;
+
+	if (IS_ERR_OR_NULL(cpr_debugfs_base)) {
+		cpr_err(cpr_vreg, "Could not create debugfs nodes since base directory is missing\n");
+		return;
+	}
+
+	cpr_vreg->debugfs = debugfs_create_dir(cpr_vreg->rdesc.name,
+						cpr_debugfs_base);
+	if (IS_ERR_OR_NULL(cpr_vreg->debugfs)) {
+		cpr_err(cpr_vreg, "debugfs directory creation failed\n");
+		return;
+	}
+
+	temp = debugfs_create_file("debug_info", S_IRUGO, cpr_vreg->debugfs,
+					cpr_vreg, &cpr_debug_info_fops);
+	if (IS_ERR_OR_NULL(temp)) {
+		cpr_err(cpr_vreg, "debug_info node creation failed\n");
+		return;
+	}
+
+	temp = debugfs_create_file("cpr_enable", S_IRUGO | S_IWUSR,
+			cpr_vreg->debugfs, cpr_vreg, &cpr_enable_fops);
+	if (IS_ERR_OR_NULL(temp)) {
+		cpr_err(cpr_vreg, "cpr_enable node creation failed\n");
+		return;
+	}
+
+	temp = debugfs_create_file("cpr_ceiling", S_IRUGO,
+			cpr_vreg->debugfs, cpr_vreg, &cpr_ceiling_fops);
+	if (IS_ERR_OR_NULL(temp)) {
+		cpr_err(cpr_vreg, "cpr_ceiling node creation failed\n");
+		return;
+	}
+
+	temp = debugfs_create_file("cpr_floor", S_IRUGO,
+			cpr_vreg->debugfs, cpr_vreg, &cpr_floor_fops);
+	if (IS_ERR_OR_NULL(temp)) {
+		cpr_err(cpr_vreg, "cpr_floor node creation failed\n");
+		return;
+	}
+
+	temp = debugfs_create_file("cpr_max_ceiling", S_IRUGO,
+			cpr_vreg->debugfs, cpr_vreg, &cpr_max_ceiling_fops);
+	if (IS_ERR_OR_NULL(temp)) {
+		cpr_err(cpr_vreg, "cpr_max_ceiling node creation failed\n");
+		return;
+	}
+
+	if (cpr_vreg->aging_info) {
+		temp = debugfs_create_file("aging_debug_info", S_IRUGO,
+					cpr_vreg->debugfs, cpr_vreg,
+					&cpr_aging_debug_info_fops);
+		if (IS_ERR_OR_NULL(temp)) {
+			cpr_err(cpr_vreg, "aging_debug_info node creation failed\n");
+			return;
+		}
+	}
+}
+
+static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg)
+{
+	debugfs_remove_recursive(cpr_vreg->debugfs);
+}
+
+static void cpr_debugfs_base_init(void)
+{
+	cpr_debugfs_base = debugfs_create_dir("cpr-regulator", NULL);
+	if (IS_ERR_OR_NULL(cpr_debugfs_base))
+		pr_err("cpr-regulator debugfs base directory creation failed\n");
+}
+
+static void cpr_debugfs_base_remove(void)
+{
+	debugfs_remove_recursive(cpr_debugfs_base);
+}
+
+#else
+
+static void cpr_debugfs_init(struct cpr_regulator *cpr_vreg)
+{}
+
+static void cpr_debugfs_remove(struct cpr_regulator *cpr_vreg)
+{}
+
+static void cpr_debugfs_base_init(void)
+{}
+
+static void cpr_debugfs_base_remove(void)
+{}
+
+#endif
+
+/**
+ * cpr_panic_callback() - panic notification callback function. This function
+ *		is invoked when a kernel panic occurs.
+ * @nfb:	Notifier block pointer of CPR regulator
+ * @event:	Value passed unmodified to notifier function
+ * @data:	Pointer passed unmodified to notifier function
+ *
+ * Return: NOTIFY_OK
+ */
+static int cpr_panic_callback(struct notifier_block *nfb,
+			unsigned long event, void *data)
+{
+	struct cpr_regulator *cpr_vreg = container_of(nfb,
+				struct cpr_regulator, panic_notifier);
+	int corner, fuse_corner, volt;
+
+	corner = cpr_vreg->corner;
+	fuse_corner = cpr_vreg->corner_map[corner];
+	if (cpr_is_allowed(cpr_vreg))
+		volt = cpr_vreg->last_volt[corner];
+	else
+		volt = cpr_vreg->open_loop_volt[corner];
+
+	cpr_err(cpr_vreg, "[corner:%d, fuse_corner:%d] = %d uV\n",
+		corner, fuse_corner, volt);
+
+	return NOTIFY_OK;
+}
+
+static int cpr_regulator_probe(struct platform_device *pdev)
+{
+	struct regulator_config reg_config = {};
+	struct cpr_regulator *cpr_vreg;
+	struct regulator_desc *rdesc;
+	struct device *dev = &pdev->dev;
+	struct regulator_init_data *init_data = pdev->dev.platform_data;
+	int rc;
+
+	if (!pdev->dev.of_node) {
+		dev_err(dev, "Device tree node is missing\n");
+		return -EINVAL;
+	}
+
+	init_data = of_get_regulator_init_data(&pdev->dev, pdev->dev.of_node);
+	if (!init_data) {
+		dev_err(dev, "regulator init data is missing\n");
+		return -EINVAL;
+	} else {
+		init_data->constraints.input_uV
+			= init_data->constraints.max_uV;
+		init_data->constraints.valid_ops_mask
+			|= REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_STATUS;
+	}
+
+	cpr_vreg = devm_kzalloc(&pdev->dev, sizeof(struct cpr_regulator),
+				GFP_KERNEL);
+	if (!cpr_vreg) {
+		dev_err(dev, "Can't allocate cpr_regulator memory\n");
+		return -ENOMEM;
+	}
+
+	cpr_vreg->dev = &pdev->dev;
+	cpr_vreg->rdesc.name = init_data->constraints.name;
+	if (cpr_vreg->rdesc.name == NULL) {
+		dev_err(dev, "regulator-name missing\n");
+		return -EINVAL;
+	}
+
+	rc = cpr_fuse_corner_array_alloc(&pdev->dev, cpr_vreg);
+	if (rc)
+		return rc;
+
+	rc = cpr_mem_acc_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "mem_acc intialization error rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = cpr_efuse_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Wrong eFuse address specified: rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = cpr_remap_efuse_data(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Could not remap fuse data: rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = cpr_check_redundant(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Could not check redundant fuse: rc=%d\n",
+			rc);
+		goto err_out;
+	}
+
+	rc = cpr_read_fuse_revision(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Could not read fuse revision: rc=%d\n", rc);
+		goto err_out;
+	}
+
+	cpr_parse_speed_bin_fuse(cpr_vreg, dev->of_node);
+	cpr_parse_pvs_version_fuse(cpr_vreg, dev->of_node);
+
+	rc = cpr_read_ro_select(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Could not read RO select: rc=%d\n", rc);
+		goto err_out;
+	}
+
+	rc = cpr_find_fuse_map_match(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Could not determine fuse mapping match: rc=%d\n",
+			rc);
+		goto err_out;
+	}
+
+	rc = cpr_voltage_plan_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Wrong DT parameter specified: rc=%d\n", rc);
+		goto err_out;
+	}
+
+	rc = cpr_pvs_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Initialize PVS wrong: rc=%d\n", rc);
+		goto err_out;
+	}
+
+	rc = cpr_vsens_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Initialize vsens configuration failed rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = cpr_apc_init(pdev, cpr_vreg);
+	if (rc) {
+		if (rc != -EPROBE_DEFER)
+			cpr_err(cpr_vreg, "Initialize APC wrong: rc=%d\n", rc);
+		goto err_out;
+	}
+
+	rc = cpr_init_cpr(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Initialize CPR failed: rc=%d\n", rc);
+		goto err_out;
+	}
+
+	rc = cpr_rpm_apc_init(pdev, cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Initialize RPM APC regulator failed rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = cpr_thermal_init(cpr_vreg);
+	if (rc) {
+		cpr_err(cpr_vreg, "Thermal intialization failed rc=%d\n", rc);
+		return rc;
+	}
+
+	if (of_property_read_bool(pdev->dev.of_node,
+				"qcom,disable-closed-loop-in-pc")) {
+		rc = cpr_init_pm_notification(cpr_vreg);
+		if (rc) {
+			cpr_err(cpr_vreg,
+				"cpr_init_pm_notification failed rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* Load per-online CPU adjustment data */
+	rc = cpr_init_per_cpu_adjustments(cpr_vreg, &pdev->dev);
+	if (rc) {
+		cpr_err(cpr_vreg, "cpr_init_per_cpu_adjustments failed: rc=%d\n",
+			rc);
+		goto err_out;
+	}
+
+	/* Parse dependency parameters */
+	if (cpr_vreg->vdd_mx) {
+		rc = cpr_parse_vdd_mx_parameters(pdev, cpr_vreg);
+		if (rc) {
+			cpr_err(cpr_vreg, "parsing vdd_mx parameters failed: rc=%d\n",
+				rc);
+			goto err_out;
+		}
+	}
+
+	cpr_efuse_free(cpr_vreg);
+
+	/*
+	 * Ensure that enable state accurately reflects the case in which CPR
+	 * is permanently disabled.
+	 */
+	cpr_vreg->enable &= !cpr_vreg->cpr_fuse_disable;
+
+	mutex_init(&cpr_vreg->cpr_mutex);
+
+	rdesc			= &cpr_vreg->rdesc;
+	rdesc->owner		= THIS_MODULE;
+	rdesc->type		= REGULATOR_VOLTAGE;
+	rdesc->ops		= &cpr_corner_ops;
+
+	reg_config.dev = &pdev->dev;
+	reg_config.init_data = init_data;
+	reg_config.driver_data = cpr_vreg;
+	reg_config.of_node = pdev->dev.of_node;
+	cpr_vreg->rdev = regulator_register(rdesc, &reg_config);
+	if (IS_ERR(cpr_vreg->rdev)) {
+		rc = PTR_ERR(cpr_vreg->rdev);
+		cpr_err(cpr_vreg, "regulator_register failed: rc=%d\n", rc);
+
+		cpr_apc_exit(cpr_vreg);
+		return rc;
+	}
+
+	platform_set_drvdata(pdev, cpr_vreg);
+	cpr_debugfs_init(cpr_vreg);
+
+	if (cpr_vreg->cpr_disable_on_temperature) {
+		rc = cpr_check_tsens(cpr_vreg);
+		if (rc < 0) {
+			cpr_err(cpr_vreg, "Unable to config CPR on tsens, rc=%d\n",
+									rc);
+			cpr_apc_exit(cpr_vreg);
+			cpr_debugfs_remove(cpr_vreg);
+			return rc;
+		}
+	}
+
+	/* Register panic notification call back */
+	cpr_vreg->panic_notifier.notifier_call = cpr_panic_callback;
+	atomic_notifier_chain_register(&panic_notifier_list,
+			&cpr_vreg->panic_notifier);
+
+	mutex_lock(&cpr_regulator_list_mutex);
+	list_add(&cpr_vreg->list, &cpr_regulator_list);
+	mutex_unlock(&cpr_regulator_list_mutex);
+
+	return 0;
+
+err_out:
+	cpr_efuse_free(cpr_vreg);
+	return rc;
+}
+
+static int cpr_regulator_remove(struct platform_device *pdev)
+{
+	struct cpr_regulator *cpr_vreg;
+
+	cpr_vreg = platform_get_drvdata(pdev);
+	if (cpr_vreg) {
+		/* Disable CPR */
+		if (cpr_is_allowed(cpr_vreg)) {
+			cpr_ctl_disable(cpr_vreg);
+			cpr_irq_set(cpr_vreg, 0);
+		}
+
+		mutex_lock(&cpr_regulator_list_mutex);
+		list_del(&cpr_vreg->list);
+		mutex_unlock(&cpr_regulator_list_mutex);
+
+		if (cpr_vreg->cpu_notifier.notifier_call)
+			unregister_hotcpu_notifier(&cpr_vreg->cpu_notifier);
+
+		if (cpr_vreg->cpr_disable_on_temperature)
+			sensor_mgr_remove_threshold(
+				&cpr_vreg->tsens_threshold_config);
+
+		atomic_notifier_chain_unregister(&panic_notifier_list,
+			&cpr_vreg->panic_notifier);
+
+		cpr_apc_exit(cpr_vreg);
+		cpr_debugfs_remove(cpr_vreg);
+		regulator_unregister(cpr_vreg->rdev);
+	}
+
+	return 0;
+}
+
+static struct of_device_id cpr_regulator_match_table[] = {
+	{ .compatible = CPR_REGULATOR_DRIVER_NAME, },
+	{}
+};
+
+static struct platform_driver cpr_regulator_driver = {
+	.driver		= {
+		.name	= CPR_REGULATOR_DRIVER_NAME,
+		.of_match_table = cpr_regulator_match_table,
+		.owner = THIS_MODULE,
+	},
+	.probe		= cpr_regulator_probe,
+	.remove		= cpr_regulator_remove,
+	.suspend	= cpr_regulator_suspend,
+	.resume		= cpr_regulator_resume,
+};
+
+static int initialize_tsens_monitor(struct cpr_regulator *cpr_vreg)
+{
+	int rc;
+
+	rc = cpr_check_tsens(cpr_vreg);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "Unable to check tsens, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = sensor_mgr_init_threshold(&cpr_vreg->tsens_threshold_config,
+				cpr_vreg->tsens_id,
+				cpr_vreg->cpr_enable_temp_threshold, /* high */
+				cpr_vreg->cpr_disable_temp_threshold, /* low */
+				tsens_threshold_notify);
+	if (rc < 0) {
+		cpr_err(cpr_vreg, "Failed to init tsens monitor, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = sensor_mgr_convert_id_and_set_threshold(
+			&cpr_vreg->tsens_threshold_config);
+	if (rc < 0)
+		cpr_err(cpr_vreg, "Failed to set tsens threshold, rc=%d\n",
+					rc);
+
+	return rc;
+}
+
+int __init cpr_regulator_late_init(void)
+{
+	int rc;
+	struct cpr_regulator *cpr_vreg;
+
+	mutex_lock(&cpr_regulator_list_mutex);
+
+	list_for_each_entry(cpr_vreg, &cpr_regulator_list, list) {
+		if (cpr_vreg->cpr_disable_on_temperature) {
+			rc = initialize_tsens_monitor(cpr_vreg);
+			if (rc)
+				cpr_err(cpr_vreg, "Failed to initialize temperature monitor, rc=%d\n",
+					rc);
+		}
+	}
+
+	mutex_unlock(&cpr_regulator_list_mutex);
+	return 0;
+}
+late_initcall(cpr_regulator_late_init);
+
+/**
+ * cpr_regulator_init() - register cpr-regulator driver
+ *
+ * This initialization function should be called in systems in which driver
+ * registration ordering must be controlled precisely.
+ */
+int __init cpr_regulator_init(void)
+{
+	static bool initialized;
+
+	if (initialized)
+		return 0;
+	else
+		initialized = true;
+
+	cpr_debugfs_base_init();
+	return platform_driver_register(&cpr_regulator_driver);
+}
+EXPORT_SYMBOL(cpr_regulator_init);
+
+static void __exit cpr_regulator_exit(void)
+{
+	platform_driver_unregister(&cpr_regulator_driver);
+	cpr_debugfs_base_remove();
+}
+
+MODULE_DESCRIPTION("CPR regulator driver");
+MODULE_LICENSE("GPL v2");
+
+arch_initcall(cpr_regulator_init);
+module_exit(cpr_regulator_exit);
diff --git a/include/linux/regulator/cpr-regulator.h b/include/linux/regulator/cpr-regulator.h
new file mode 100644
index 0000000..7a04e70
--- /dev/null
+++ b/include/linux/regulator/cpr-regulator.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2013-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.
+ */
+
+#ifndef __REGULATOR_CPR_REGULATOR_H__
+#define __REGULATOR_CPR_REGULATOR_H__
+
+#include <linux/init.h>
+
+#ifdef CONFIG_REGULATOR_CPR
+
+int __init cpr_regulator_init(void);
+
+#else
+
+static inline int __init cpr_regulator_init(void)
+{
+	return -ENODEV;
+}
+
+#endif /* CONFIG_REGULATOR_CPR */
+
+#endif /* __REGULATOR_CPR_REGULATOR_H__ */