Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c79d132d authored by David Collins's avatar David Collins
Browse files

regulator: cpr3-mmss-regulator: add support for msmcobalt CPR4 controller



Add support for the fuse layout and hardware constraints of the
msmcobalt CPR4 controller which is used to manage the GPU supply
regulator.  Also update the cpr3-regulator core driver in order
to support CPR register writing for this device.

Change-Id: I408854a93e820c168551bcfec7d4f87cdbe5d638
CRs-Fixed: 986619
Signed-off-by: default avatarDavid Collins <collinsd@codeaurora.org>
parent e551e7a6
Loading
Loading
Loading
Loading
+77 −3
Original line number Diff line number Diff line
@@ -27,13 +27,13 @@ MMSS specific properties:
- compatible
	Usage:      required
	Value type: <string>
	Definition: should be "qcom,cpr3-msm8996-mmss-regulator"
	Definition: should be one of the following:
		    "qcom,cpr3-msm8996-v1-mmss-regulator",
		    "qcom,cpr3-msm8996-v2-mmss-regulator",
		    "qcom,cpr3-msm8996-v3-mmss-regulator",
		    "qcom,cpr3-msm8996-mmss-regulator",
		    "qcom,cpr3-msm8996pro-mmss-regulator".
		    "qcom,cpr3-msm8996pro-mmss-regulator",
		    "qcom,cpr4-msmcobalt-mmss-regulator".
		    If the SoC revision is not specified, then it is assumed to
		    be the most recent revision of MSM8996, i.e. v3.

@@ -50,7 +50,39 @@ MMSS specific properties:
	Value type: <stringlist>
	Definition: Clock names.  This list must match up 1-to-1 with the clocks
		    specified in the 'clocks' property. "core_clk", "iface_clk",
		    and "bus_clk" must be specified.
		    and "bus_clk" must be specified.  Note that "iface_clk" is
		    not required for devices with compatible =
		    "qcom,cpr4-msmcobalt-mmss-regulator".

- qcom,cpr-temp-point-map
	Usage:      Required if qcom,corner-allow-temp-adjustment is specified
		    for at least one of the CPR3 regulators.
	Value type: <prop-encoded-array>
	Definition: The temperature points in decidegrees Celsius which indicate
		    the range of temperature bands supported. If t1, t2, and t3
		    are the temperature points, then the temperature bands are:
		    (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).  1 to 3
		    temperature points should be specified.

- qcom,cpr-initial-temp-band
	Usage:      Required if qcom,cpr-temp-point-map is specified.
	Value type: <u32>
	Definition: The initial temp band considering 0-based index at which
		    the baseline target quotients are derived and fused.
		    Supported values: 0 to number of elements in
		    qcom,cpr-temp-point-map.

- qcom,cpr-step-quot-fixed
	Usage:      Optional for controllers with compatible =
		    "qcom,cpr4-msmcobalt-mmss-regulator"; unsupported for
		    all others.
	Value type: <u32>
	Definition: Fixed step quotient value used by controller for applying
		    the SDELTA margin adjustments on the programmed target
		    quotient values. The step quotient is the number of
		    additional ring oscillator ticks observed for each
		    qcom,voltage-step increase in vdd-supply output voltage.
		    Supported values: 0 - 63.

=================================================
Second Level Nodes - CPR Threads for a Controller
@@ -163,6 +195,48 @@ MMSS specific properties:
		    Values 1 to qcom,cpr-fuse-corners denote the specific fuse
		    corner that should be used by a given voltage corner.

- qcom,corner-allow-temp-adjustment
	Usage:      Optional for controllers with compatible =
		    "qcom,cpr4-msmcobalt-mmss-regulator"; unsupported for
		    all others.
	Value type: <prop-encoded-array>
	Definition: A list of integer tuples which each define the CPR
		    temperature adjustment feature enable state for each voltage
		    corner in order from lowest to highest. Each element in the
		    tuple should be either 0 (temperature adjustment not
		    allowed) or 1 (temperature adjustment allowed).

		    The list must contain qcom,cpr-fuse-combos number of tuples
		    in which case the tuples are matched to fuse combinations
		    1-to-1 or qcom,cpr-speed-bins number of tuples in which case
		    the tuples are matched to speed bins 1-to-1 or exactly 1
		    tuple which is used regardless of the fuse combination and
		    speed bin found on a given chip.

		    Each tuple must be of the length defined in the
		    corresponding element of the qcom,cpr-corners property or
		    the qcom,cpr-speed-bin-corners property.  A single tuple may
		    only be specified if all of the corner counts in
		    qcom,cpr-corners are the same.

- qcom,cpr-cornerX-temp-core-voltage-adjustment
	Usage:      Required if qcom,corner-allow-temp-adjustment is specified
		    for this CPR3 regulator.
	Value type: <prop-encoded-array>
	Definition: A list of integer tuples for cornerX. The possible values
		    for X are 1 to the max value specified in qcom,cpr-corners.
		    Each tuple defines the temperature based voltage adjustment
		    in microvolts for each temperature band from lowest to
		    highest.  Each tuple must have a number of elements equal to
		    (the number of elements in qcom,cpr-ctrl-temp-point-map + 1)

		    The tuple list must contain qcom,cpr-fuse-combos number of
		    tuples in which case the tuples are matched to fuse
		    combinations 1-to-1 or qcom,cpr-speed-bins number of tuples
		    in which case the tuples are matched to speed bins 1-to-1 or
		    exactly 1 list which is used regardless of the fuse
		    combination and speed bin found on a given chip.

Note that the qcom,cpr-closed-loop-voltage-fuse-adjustment property is not
meaningful for MMSS CPR3 regulator nodes since target quotients are not defined
in fuses.
+218 −25
Original line number Diff line number Diff line
@@ -71,6 +71,9 @@ struct cpr3_msm8996_mmss_fuses {
 */
#define CPR3_MSM8996PRO_MMSS_FUSE_COMBO_COUNT	16

/* Fuse combos 0 -  7 map to CPR fusing revision 0 - 7 */
#define CPR3_MSMCOBALT_MMSS_FUSE_COMBO_COUNT	8

/*
 * MSM8996 MMSS fuse parameter locations:
 *
@@ -121,7 +124,42 @@ static const struct cpr3_fuse_param msm8996pro_mmss_speed_bin_param[] = {
	{},
};

/* MSMCOBALT MMSS fuse parameter locations: */
static const struct cpr3_fuse_param
msmcobalt_mmss_init_voltage_param[MSM8996_MMSS_FUSE_CORNERS][2] = {
	{{65, 39, 43}, {} },
	{{65, 34, 38}, {} },
	{{65, 29, 33}, {} },
	{{65, 24, 28}, {} },
};

static const struct cpr3_fuse_param msmcobalt_cpr_fusing_rev_param[] = {
	{39, 48, 50},
	{},
};

static const struct cpr3_fuse_param msmcobalt_cpr_limitation_param[] = {
	{41, 46, 47},
	{},
};

static const struct cpr3_fuse_param
msmcobalt_mmss_aging_init_quot_diff_param[] = {
	{65, 60, 63},
	{66, 0, 3},
	{},
};

static const struct cpr3_fuse_param
msmcobalt_mmss_offset_voltage_param[MSM8996_MMSS_FUSE_CORNERS][2] = {
	{{65, 56, 59}, {} },
	{{65, 52, 55}, {} },
	{{65, 48, 51}, {} },
	{{65, 44, 47}, {} },
};

#define MSM8996PRO_SOC_ID			4
#define MSMCOBALT_SOC_ID			5

/*
 * Some initial msm8996 parts cannot be used in a meaningful way by software.
@@ -152,6 +190,13 @@ static const int msm8996pro_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
	1065000,
};

static const int msmcobalt_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
	632000,
	768000,
	896000,
	1032000,
};

#define MSM8996_MMSS_FUSE_STEP_VOLT		10000
#define MSM8996_MMSS_OFFSET_FUSE_STEP_VOLT	10000
#define MSM8996_MMSS_VOLTAGE_FUSE_SIZE		5
@@ -165,6 +210,18 @@ static const int msm8996pro_mmss_fuse_ref_volt[MSM8996_MMSS_FUSE_CORNERS] = {
#define MSM8996_MMSS_AGING_SENSOR_ID		29
#define MSM8996_MMSS_AGING_BYPASS_MASK0		(GENMASK(23, 0))

#define MSMCOBALT_MMSS_AGING_INIT_QUOT_DIFF_SCALE	1
#define MSMCOBALT_MMSS_AGING_INIT_QUOT_DIFF_SIZE	8

#define MSMCOBALT_MMSS_CPR_SENSOR_COUNT			35

#define MSMCOBALT_MMSS_AGING_SENSOR_ID			17
#define MSMCOBALT_MMSS_AGING_BYPASS_MASK0		0

#define MSMCOBALT_MMSS_MAX_TEMP_POINTS			3
#define MSMCOBALT_MMSS_TEMP_SENSOR_ID_START		12
#define MSMCOBALT_MMSS_TEMP_SENSOR_ID_END		13

/**
 * cpr3_msm8996_mmss_read_fuse_data() - load MMSS specific fuse parameter values
 * @vreg:		Pointer to the CPR3 regulator
@@ -196,7 +253,10 @@ static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)
		cpr3_info(vreg, "speed bin = %llu\n", fuse->speed_bin);
	}

	rc = cpr3_read_fuse_param(base, msm8996_cpr_fusing_rev_param,
	rc = cpr3_read_fuse_param(base,
			vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID
				? msmcobalt_cpr_fusing_rev_param
				: msm8996_cpr_fusing_rev_param,
			&fuse->cpr_fusing_rev);
	if (rc) {
		cpr3_err(vreg, "Unable to read CPR fusing revision fuse, rc=%d\n",
@@ -205,7 +265,10 @@ static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)
	}
	cpr3_info(vreg, "CPR fusing revision = %llu\n", fuse->cpr_fusing_rev);

	rc = cpr3_read_fuse_param(base, msm8996_cpr_limitation_param,
	rc = cpr3_read_fuse_param(base,
			vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID
				? msmcobalt_cpr_limitation_param
				: msm8996_cpr_limitation_param,
			&fuse->limitation);
	if (rc) {
		cpr3_err(vreg, "Unable to read CPR limitation fuse, rc=%d\n",
@@ -218,7 +281,10 @@ static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)
			  == MSM8996_CPR_LIMITATION_NO_CPR_OR_INTERPOLATION
		? "CPR disabled and no interpolation" : "none");

	rc = cpr3_read_fuse_param(base, msm8996_mmss_aging_init_quot_diff_param,
	rc = cpr3_read_fuse_param(base,
			vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID
				? msmcobalt_mmss_aging_init_quot_diff_param
				: msm8996_mmss_aging_init_quot_diff_param,
			&fuse->aging_init_quot_diff);
	if (rc) {
		cpr3_err(vreg, "Unable to read aging initial quotient difference fuse, rc=%d\n",
@@ -228,7 +294,9 @@ static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)

	for (i = 0; i < MSM8996_MMSS_FUSE_CORNERS; i++) {
		rc = cpr3_read_fuse_param(base,
			msm8996_mmss_init_voltage_param[i],
			vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID
				? msmcobalt_mmss_init_voltage_param[i]
				: msm8996_mmss_init_voltage_param[i],
			&fuse->init_voltage[i]);
		if (rc) {
			cpr3_err(vreg, "Unable to read fuse-corner %d initial voltage fuse, rc=%d\n",
@@ -237,7 +305,9 @@ static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)
		}

		rc = cpr3_read_fuse_param(base,
			msm8996_mmss_offset_voltage_param[i],
			vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID
				? msmcobalt_mmss_offset_voltage_param[i]
				: msm8996_mmss_offset_voltage_param[i],
			&fuse->offset_voltage[i]);
		if (rc) {
			cpr3_err(vreg, "Unable to read fuse-corner %d offset voltage fuse, rc=%d\n",
@@ -246,7 +316,10 @@ static int cpr3_msm8996_mmss_read_fuse_data(struct cpr3_regulator *vreg)
		}
	}

	if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID) {
	if (vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID) {
		combo_max = CPR3_MSMCOBALT_MMSS_FUSE_COMBO_COUNT;
		vreg->fuse_combo = fuse->cpr_fusing_rev;
	} else if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID) {
		combo_max = CPR3_MSM8996PRO_MMSS_FUSE_COMBO_COUNT;
		vreg->fuse_combo = fuse->cpr_fusing_rev + 8 * fuse->speed_bin;
	} else {
@@ -322,6 +395,7 @@ static int cpr3_msm8996_mmss_apply_closed_loop_offset_voltages(
			struct cpr3_regulator *vreg, int *volt_adjust)
{
	struct cpr3_msm8996_mmss_fuses *fuse = vreg->platform_fuses;
	const struct cpr3_fuse_param (*offset_param)[2];
	u32 *corner_map;
	int *volt_offset;
	int rc = 0, i, fuse_len;
@@ -347,9 +421,12 @@ static int cpr3_msm8996_mmss_apply_closed_loop_offset_voltages(
	if (rc)
		goto done;

	offset_param = vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID
			? msmcobalt_mmss_offset_voltage_param
			: msm8996_mmss_offset_voltage_param;
	for (i = 0; i < vreg->fuse_corner_count; i++) {
		fuse_len = msm8996_mmss_offset_voltage_param[i][0].bit_end + 1
			   - msm8996_mmss_offset_voltage_param[i][0].bit_start;
		fuse_len = offset_param[i][0].bit_end + 1
			   - offset_param[i][0].bit_start;
		volt_offset[i] = cpr3_convert_open_loop_voltage_fuse(
			0, MSM8996_MMSS_OFFSET_FUSE_STEP_VOLT,
			fuse->offset_voltage[i], fuse_len);
@@ -423,7 +500,7 @@ static void cpr3_mmss_enforce_dec_quotient_monotonicity(

	for (i = vreg->corner_count - 2; i >= 0; i--) {
		for (j = 0; j < CPR3_RO_COUNT; j++) {
			if (vreg->corner[i].target_quot[j]
			if (vreg->corner[i + 1].target_quot[j]
			    && vreg->corner[i].target_quot[j]
					> vreg->corner[i + 1].target_quot[j]) {
				cpr3_debug(vreg, "corner %d RO%u target quot=%u > corner %d RO%u target quot=%u; overriding: corner %d RO%u target quot=%u\n",
@@ -617,7 +694,9 @@ static int cpr3_msm8996_mmss_calculate_open_loop_voltages(
		goto done;
	}

	if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID)
	if (vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID)
		ref_volt = msmcobalt_mmss_fuse_ref_volt;
	else if (vreg->thread->ctrl->soc_revision == MSM8996PRO_SOC_ID)
		ref_volt = msm8996pro_mmss_fuse_ref_volt;
	else
		ref_volt = msm8996_mmss_fuse_ref_volt;
@@ -773,15 +852,27 @@ static int cpr3_mmss_init_aging(struct cpr3_controller *ctrl)
	if (!ctrl->aging_sensor)
		return -ENOMEM;

	ctrl->aging_sensor->sensor_id = MSM8996_MMSS_AGING_SENSOR_ID;
	ctrl->aging_sensor->bypass_mask[0] = MSM8996_MMSS_AGING_BYPASS_MASK0;
	ctrl->aging_sensor->ro_scale = aging_ro_scale;

	if (vreg->thread->ctrl->soc_revision == MSMCOBALT_SOC_ID) {
		ctrl->aging_sensor->sensor_id = MSMCOBALT_MMSS_AGING_SENSOR_ID;
		ctrl->aging_sensor->bypass_mask[0]
					= MSMCOBALT_MMSS_AGING_BYPASS_MASK0;
		ctrl->aging_sensor->init_quot_diff
			= cpr3_convert_open_loop_voltage_fuse(0,
				MSMCOBALT_MMSS_AGING_INIT_QUOT_DIFF_SCALE,
				fuse->aging_init_quot_diff,
				MSMCOBALT_MMSS_AGING_INIT_QUOT_DIFF_SIZE);
	} else {
		ctrl->aging_sensor->sensor_id = MSM8996_MMSS_AGING_SENSOR_ID;
		ctrl->aging_sensor->bypass_mask[0]
					= MSM8996_MMSS_AGING_BYPASS_MASK0;
		ctrl->aging_sensor->init_quot_diff
			= cpr3_convert_open_loop_voltage_fuse(0,
				MSM8996_MMSS_AGING_INIT_QUOT_DIFF_SCALE,
				fuse->aging_init_quot_diff,
				MSM8996_MMSS_AGING_INIT_QUOT_DIFF_SIZE);
	}

	cpr3_debug(ctrl, "sensor %u aging init quotient diff = %d, aging RO scale = %u QUOT/V\n",
		ctrl->aging_sensor->sensor_id,
@@ -862,11 +953,83 @@ static int cpr3_mmss_init_thread(struct cpr3_thread *thread)
		return rc;
	}

	if (thread->ctrl->soc_revision == MSMCOBALT_SOC_ID) {
		rc = cpr4_parse_core_count_temp_voltage_adj(vreg, false);
		if (rc) {
			cpr3_err(vreg, "unable to parse temperature based voltage adjustments, rc=%d\n",
				 rc);
			return rc;
		}
	}

	cpr3_mmss_print_settings(vreg);

	return 0;
}

/**
 * cpr4_mmss_parse_temp_adj_properties() - parse temperature based
 *		adjustment properties from device tree
 * @ctrl:	Pointer to the CPR3 controller
 *
 * Return: 0 on success, errno on failure
 */
static int cpr4_mmss_parse_temp_adj_properties(struct cpr3_controller *ctrl)
{
	struct device_node *of_node = ctrl->dev->of_node;
	int rc, len, temp_point_count;

	if (!of_find_property(of_node, "qcom,cpr-temp-point-map", &len))
		return 0;

	temp_point_count = len / sizeof(u32);
	if (temp_point_count <= 0
	    || temp_point_count > MSMCOBALT_MMSS_MAX_TEMP_POINTS) {
		cpr3_err(ctrl, "invalid number of temperature points %d > %d (max)\n",
			 temp_point_count, MSMCOBALT_MMSS_MAX_TEMP_POINTS);
		return -EINVAL;
	}

	ctrl->temp_points = devm_kcalloc(ctrl->dev, temp_point_count,
					sizeof(*ctrl->temp_points), GFP_KERNEL);
	if (!ctrl->temp_points)
		return -ENOMEM;

	rc = of_property_read_u32_array(of_node, "qcom,cpr-temp-point-map",
					ctrl->temp_points, temp_point_count);
	if (rc) {
		cpr3_err(ctrl, "error reading property qcom,cpr-temp-point-map, rc=%d\n",
			 rc);
		return rc;
	}

	/*
	 * If t1, t2, and t3 are the temperature points, then the temperature
	 * bands are: (-inf, t1], (t1, t2], (t2, t3], and (t3, inf).
	 */
	ctrl->temp_band_count = temp_point_count + 1;

	rc = of_property_read_u32(of_node, "qcom,cpr-initial-temp-band",
				  &ctrl->initial_temp_band);
	if (rc) {
		cpr3_err(ctrl, "error reading qcom,cpr-initial-temp-band, rc=%d\n",
			rc);
		return rc;
	}

	if (ctrl->initial_temp_band >= ctrl->temp_band_count) {
		cpr3_err(ctrl, "Initial temperature band value %d should be in range [0 - %d]\n",
			ctrl->initial_temp_band, ctrl->temp_band_count - 1);
		return -EINVAL;
	}

	ctrl->temp_sensor_id_start = MSMCOBALT_MMSS_TEMP_SENSOR_ID_START;
	ctrl->temp_sensor_id_end = MSMCOBALT_MMSS_TEMP_SENSOR_ID_END;
	ctrl->allow_temp_adj = true;

	return rc;
}

/**
 * cpr3_mmss_init_controller() - perform MMSS CPR3 controller specific
 *		initializations
@@ -886,7 +1049,15 @@ static int cpr3_mmss_init_controller(struct cpr3_controller *ctrl)
		return rc;
	}

	ctrl->sensor_count = MSM8996_MMSS_CPR_SENSOR_COUNT;
	if (ctrl->soc_revision == MSMCOBALT_SOC_ID) {
		rc = cpr4_mmss_parse_temp_adj_properties(ctrl);
		if (rc)
			return rc;
	}

	ctrl->sensor_count = ctrl->soc_revision == MSMCOBALT_SOC_ID
				? MSMCOBALT_MMSS_CPR_SENSOR_COUNT
				: MSM8996_MMSS_CPR_SENSOR_COUNT;

	/*
	 * MMSS only has one thread (0) so the zeroed array does not need
@@ -898,16 +1069,34 @@ static int cpr3_mmss_init_controller(struct cpr3_controller *ctrl)
		return -ENOMEM;

	ctrl->cpr_clock_rate = MSM8996_MMSS_CPR_CLOCK_RATE;
	ctrl->ctrl_type = CPR_CTRL_TYPE_CPR3;
	ctrl->ctrl_type = ctrl->soc_revision == MSMCOBALT_SOC_ID
				? CPR_CTRL_TYPE_CPR4 : CPR_CTRL_TYPE_CPR3;

	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
		/*
		 * Use fixed step quotient if specified otherwise use dynamic
		 * calculated per RO step quotient
		 */
		of_property_read_u32(ctrl->dev->of_node,
				     "qcom,cpr-step-quot-fixed",
				     &ctrl->step_quot_fixed);
		ctrl->use_dynamic_step_quot = !ctrl->step_quot_fixed;
	}

	ctrl->iface_clk = devm_clk_get(ctrl->dev, "iface_clk");
	if (IS_ERR(ctrl->iface_clk)) {
		rc = PTR_ERR(ctrl->iface_clk);
		if (rc != -EPROBE_DEFER)
			cpr3_err(ctrl, "unable request interface clock, rc=%d\n",
		if (ctrl->soc_revision == MSMCOBALT_SOC_ID) {
			/* iface_clk is optional for msmcobalt */
			ctrl->iface_clk = NULL;
		} else if (rc == -EPROBE_DEFER) {
			return rc;
		} else {
			cpr3_err(ctrl, "unable to request interface clock, rc=%d\n",
				rc);
			return rc;
		}
	}

	ctrl->bus_clk = devm_clk_get(ctrl->dev, "bus_clk");
	if (IS_ERR(ctrl->bus_clk)) {
@@ -958,6 +1147,10 @@ static struct of_device_id cpr_regulator_match_table[] = {
		.compatible = "qcom,cpr3-msm8996pro-mmss-regulator",
		.data = (void *)(uintptr_t)MSM8996PRO_SOC_ID,
	},
	{
		.compatible = "qcom,cpr4-msmcobalt-mmss-regulator",
		.data = (void *)(uintptr_t)MSMCOBALT_SOC_ID,
	},
	{}
};

+118 −74
Original line number Diff line number Diff line
@@ -698,6 +698,7 @@ static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
	int thread_id = 0;
	u64 temp;

	if (ctrl->supports_hw_closed_loop) {
		if (ctrl->saw_use_unit_mV)
			pmic_step_size = ctrl->step_volt / 1000;
		cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
@@ -716,8 +717,8 @@ static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
					<< CPR4_SAW_ERROR_STEP_LIMIT_UP_SHIFT));

		/*
	 * Enable thread aggregation regardless of which threads are enabled
	 * or disabled.
		 * Enable thread aggregation regardless of which threads are
		 * enabled or disabled.
		 */
		cpr3_masked_write(ctrl, CPR4_REG_CPR_TIMER_CLAMP,
				  CPR4_CPR_TIMER_CLAMP_THREAD_AGGREGATION_EN,
@@ -741,13 +742,15 @@ static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
		case 1:
			/* Disable unused thread */
			thread_id = ctrl->thread[0].thread_id ? 0 : 1;
		cpr3_masked_write(ctrl, CPR4_REG_CPR_MASK_THREAD(thread_id),
			cpr3_masked_write(ctrl,
				CPR4_REG_CPR_MASK_THREAD(thread_id),
				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK,
				CPR4_CPR_MASK_THREAD_DISABLE_THREAD
				    | CPR4_CPR_MASK_THREAD_RO_MASK4THREAD_MASK);
			break;
		}
	}

	if (!ctrl->allow_core_count_adj && !ctrl->allow_temp_adj
		&& !ctrl->allow_boost) {
@@ -758,6 +761,7 @@ static int cpr3_regulator_init_cpr4(struct cpr3_controller *ctrl)
		return rc;
	}

	if (ctrl->supports_hw_closed_loop)
		cpr3_masked_write(ctrl, CPR4_REG_MARGIN_ADJ_CTL,
				  CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN,
				  CPR4_MARGIN_ADJ_CTL_TIMER_SETTLE_VOLTAGE_EN);
@@ -1001,8 +1005,10 @@ static int cpr3_controller_program_sdelta(struct cpr3_controller *ctrl)
		max_core_count << CPR4_MARGIN_ADJ_CTL_MAX_NUM_CORES_SHIFT
		| ((sdelta->allow_core_count_adj || sdelta->allow_boost)
			? CPR4_MARGIN_ADJ_CTL_CORE_ADJ_EN : 0)
		| (sdelta->allow_temp_adj ? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
		| ((ctrl->use_hw_closed_loop && !sdelta->allow_boost)
		| ((sdelta->allow_temp_adj && ctrl->supports_hw_closed_loop)
			? CPR4_MARGIN_ADJ_CTL_TEMP_ADJ_EN : 0)
		| (((ctrl->use_hw_closed_loop && !sdelta->allow_boost)
		    || !ctrl->supports_hw_closed_loop)
			? CPR4_MARGIN_ADJ_CTL_KV_MARGIN_ADJ_EN : 0)
		| (sdelta->allow_boost
			?  CPR4_MARGIN_ADJ_CTL_BOOST_EN : 0));
@@ -1469,7 +1475,6 @@ static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
			cpr3_debug(ctrl, "PD_THROTTLE=0x%08X\n",
				ctrl->proc_clock_throttle);
		}
	}

		if ((ctrl->use_hw_closed_loop ||
		     ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) &&
@@ -1482,7 +1487,8 @@ static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
			}

			if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3) {
			rc = msm_spm_avs_enable_irq(0, MSM_SPM_AVS_IRQ_MAX);
				rc = msm_spm_avs_enable_irq(0,
							   MSM_SPM_AVS_IRQ_MAX);
				if (rc) {
					cpr3_err(ctrl, "could not enable max IRQ, rc=%d\n",
						rc);
@@ -1490,6 +1496,7 @@ static int cpr3_regulator_init_ctrl(struct cpr3_controller *ctrl)
				}
			}
		}
	}

	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
		rc = cpr3_regulator_init_cpr4(ctrl);
@@ -2531,8 +2538,8 @@ static int cpr3_regulator_scale_vdd_voltage(struct cpr3_controller *ctrl,
		return rc;
	}

	if (new_volt == last_volt &&
		ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
	if (new_volt == last_volt && ctrl->supports_hw_closed_loop
	    && ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
		/*
		 * CPR4 features enforce voltage reprogramming when the last
		 * set voltage and new set voltage are same. This way, we can
@@ -2637,6 +2644,36 @@ static int cpr3_regulator_get_dynamic_floor_volt(struct cpr3_controller *ctrl,
	return dynamic_floor_volt;
}

/**
 * cpr3_regulator_max_sdelta_diff() - returns the maximum voltage difference in
 *		microvolts that can result from different operating conditions
 *		for the specified sdelta struct
 * @sdelta:		Pointer to the sdelta structure
 * @step_volt:		Step size in microvolts between available set
 *			points of the VDD supply.
 *
 * Return: voltage difference between the highest and lowest adjustments if
 *	sdelta and sdelta->table are valid, else 0.
 */
static int cpr3_regulator_max_sdelta_diff(const struct cpr4_sdelta *sdelta,
				int step_volt)
{
	int i, j, index, sdelta_min = INT_MAX, sdelta_max = INT_MIN;

	if (!sdelta || !sdelta->table)
		return 0;

	for (i = 0; i < sdelta->max_core_count; i++) {
		for (j = 0; j < sdelta->temp_band_count; j++) {
			index = i * sdelta->temp_band_count + j;
			sdelta_min = min(sdelta_min, sdelta->table[index]);
			sdelta_max = max(sdelta_max, sdelta->table[index]);
		}
	}

	return (sdelta_max - sdelta_min) * step_volt;
}

/**
 * cpr3_regulator_aggregate_sdelta() - check open-loop voltages of current
 *		aggregated corner and current corner of a given regulator
@@ -3080,19 +3117,27 @@ static int _cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)

	if (ctrl->cpr_enabled && ctrl->last_corner_was_closed_loop) {
		/*
		 * Always program open-loop voltage for CPR4 controller.
		 * Storing last closed loop voltage in corner structure would
		 * help debug.
		 * Always program open-loop voltage for CPR4 controllers which
		 * support hardware closed-loop.  Storing the last closed loop
		 * voltage in corner structure can still help with debugging.
		 */
		new_volt = (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3
				? aggr_corner.last_volt
				: aggr_corner.open_loop_volt);
		if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)
			new_volt = aggr_corner.last_volt;
		else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
			 && ctrl->supports_hw_closed_loop)
			new_volt = aggr_corner.open_loop_volt;
		else
			new_volt = min(aggr_corner.last_volt +
			      cpr3_regulator_max_sdelta_diff(aggr_corner.sdelta,
							     ctrl->step_volt),
				       aggr_corner.ceiling_volt);
	} else {
		new_volt = aggr_corner.open_loop_volt;
		aggr_corner.last_volt = aggr_corner.open_loop_volt;
	}

	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
	if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
	    && ctrl->supports_hw_closed_loop) {
		/*
		 * Store last aggregated corner open-loop voltage in vdd_volt
		 * which is used when programming current aggregated corner
@@ -3168,7 +3213,8 @@ static int _cpr3_regulator_update_ctrl_state(struct cpr3_controller *ctrl)
		 * re-enabling CPR loop operation.
		 */
		wmb();
	} else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4) {
	} else if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4
		   && ctrl->vdd_limit_regulator) {
		/* Set ceiling and floor limits in hardware */
		rc = regulator_set_voltage(ctrl->vdd_limit_regulator,
			aggr_corner.floor_volt,
@@ -6051,9 +6097,7 @@ int cpr3_regulator_unregister(struct cpr3_controller *ctrl)

	cpr3_closed_loop_disable(ctrl);

	if ((ctrl->use_hw_closed_loop
			|| ctrl->ctrl_type == CPR_CTRL_TYPE_CPR4)
		&& ctrl->ctrl_type != CPR_CTRL_TYPE_CPRH) {
	if (ctrl->vdd_limit_regulator) {
		regulator_disable(ctrl->vdd_limit_regulator);

		if (ctrl->ctrl_type == CPR_CTRL_TYPE_CPR3)