diff mbox series

[3/3] power: avs: qcom-cpr: add support to msm8939

Message ID 20200930062603.29009-4-jun.nie@linaro.org (mailing list archive)
State Not Applicable, archived
Headers show
Series Support CPR on msm8939 | expand

Commit Message

Jun Nie Sept. 30, 2020, 6:26 a.m. UTC
Memory power supply voltage should be higher than CPU voltage on
msm8939, so add the memory voltage control in cpr.
quot_offset_scale is missing on msm8939, no scaling is expected
in this case and do not try to read related fuse.
Corner adjustments support is added.

Signed-off-by: Jun Nie <jun.nie@linaro.org>
---
 drivers/power/avs/qcom-cpr.c | 366 ++++++++++++++++++++++++++++++++---
 1 file changed, 343 insertions(+), 23 deletions(-)

Comments

kernel test robot Oct. 1, 2020, 12:59 a.m. UTC | #1
Hi Jun,

I love your patch! Perhaps something to improve:

[auto build test WARNING on pm/linux-next]
[also build test WARNING on robh/for-next linus/master v5.9-rc7 next-20200930]
[cannot apply to battery/master]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Jun-Nie/Support-CPR-on-msm8939/20200930-142825
base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
config: x86_64-randconfig-m031-20200930 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-15) 9.3.0

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

smatch warnings:
drivers/power/avs/qcom-cpr.c:1957 cpr_fuse_match_tuple() warn: always true condition '(drv->cpr_fuse_map_match != (-1)) => (0-u16max != (-1))'

vim +1957 drivers/power/avs/qcom-cpr.c

  1879	
  1880	/* Match with map table to find out entry index that to retrieve adjustment
  1881	 * array from properties lookup table.
  1882	 */
  1883	#define FUSE_MATCH_TUPLE_SIZE 3
  1884	static int cpr_fuse_match_tuple(struct device *dev, struct cpr_drv *drv)
  1885	{
  1886		u32 cpr_rev, cpr_rev_high;
  1887		u32 pvs_ver, pvs_ver_high;
  1888		struct property *pp;
  1889		int i, tuple_size = FUSE_MATCH_TUPLE_SIZE;
  1890		int ret, len;
  1891		u32 *tmp, speedbin;
  1892	
  1893		drv->cpr_fuse_map_match = FUSE_MATCH_ANY;
  1894		ret = cpr_read_efuse(dev, "cpr_fuse_revision", &cpr_rev);
  1895		if (ret)
  1896			return ret;
  1897	
  1898		ret = cpr_read_efuse(dev, "cpr_fuse_revision_high", &cpr_rev_high);
  1899		if (!ret)
  1900			cpr_rev |= cpr_rev_high << 1;
  1901	
  1902		dev_info(dev, "fuse revision %d\n", cpr_rev);
  1903	
  1904		pp = of_find_property(dev->of_node, "fuse-version-map", &len);
  1905		if (!pp)
  1906			return 0;
  1907	
  1908		ret = cpr_read_efuse(dev, "cpr_pvs_version", &pvs_ver);
  1909		if (ret)
  1910			return ret;
  1911	
  1912		ret = cpr_read_efuse(dev, "cpr_pvs_version_high", &pvs_ver_high);
  1913		if (!ret)
  1914			pvs_ver |= pvs_ver_high << 1;
  1915	
  1916		ret = cpr_read_efuse(dev, "cpr_speedbin", &speedbin);
  1917	
  1918		dev_info(dev, "pvs_version %d, speed_bin %d\n", pvs_ver, speedbin);
  1919	
  1920		drv->cpr_fuse_map_count = len / (sizeof(u32) * tuple_size);
  1921	
  1922		if (len == 0 || len % (sizeof(u32) * tuple_size)) {
  1923			dev_err(dev, "fuse-version-map length=%d is invalid\n",
  1924				len);
  1925			return -EINVAL;
  1926		}
  1927	
  1928		tmp = kzalloc(len, GFP_KERNEL);
  1929		if (!tmp)
  1930			return -ENOMEM;
  1931	
  1932		ret = of_property_read_u32_array(dev->of_node, "fuse-version-map",
  1933					tmp, drv->cpr_fuse_map_count * tuple_size);
  1934		if (ret) {
  1935			dev_err(dev, "could not read fuse-version-map, rc=%d\n",
  1936				ret);
  1937			return ret;
  1938		}
  1939	
  1940		/*
  1941		 * fuse-version-map tuple format:
  1942		 * <speed_bin, pvs_version, cpr_fuse_revision
  1943		 */
  1944		for (i = 0; i < drv->cpr_fuse_map_count; i++) {
  1945			if (tmp[i * tuple_size] != speedbin
  1946			    && tmp[i * tuple_size] != FUSE_MATCH_ANY)
  1947				continue;
  1948			if (tmp[i * tuple_size + 1] != pvs_ver
  1949			    && tmp[i * tuple_size + 1] != FUSE_MATCH_ANY)
  1950				continue;
  1951			if (tmp[i * tuple_size + 2] != cpr_rev
  1952			    && tmp[i * tuple_size + 2] != FUSE_MATCH_ANY)
  1953				continue;
  1954			drv->cpr_fuse_map_match = i;
  1955			break;
  1956		}
> 1957		if (drv->cpr_fuse_map_match != FUSE_MATCH_ANY) {
  1958			dev_info(dev, "fuse map tuple match found: %d\n",
  1959				drv->cpr_fuse_map_match);
  1960		} else {
  1961			dev_warn(dev, "fuse map tuple match not found\n");
  1962			drv->cpr_fuse_map_match = 0;
  1963			drv->cpr_fuse_map_count = 1;
  1964		}
  1965		return 0;
  1966	}
  1967	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Jun Nie Oct. 7, 2020, 3:22 a.m. UTC | #2
kernel test robot <lkp@intel.com> 于2020年10月1日周四 上午9:00写道:
>
> Hi Jun,
>
> I love your patch! Perhaps something to improve:
>
> [auto build test WARNING on pm/linux-next]
> [also build test WARNING on robh/for-next linus/master v5.9-rc7 next-20200930]
> [cannot apply to battery/master]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
> https://git-scm.com/docs/git-format-patch]
>
> url:    https://github.com/0day-ci/linux/commits/Jun-Nie/Support-CPR-on-msm8939/20200930-142825
> base:   https://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git linux-next
> config: x86_64-randconfig-m031-20200930 (attached as .config)
> compiler: gcc-9 (Debian 9.3.0-15) 9.3.0
>
> If you fix the issue, kindly add following tag as appropriate
> Reported-by: kernel test robot <lkp@intel.com>
>
> smatch warnings:
> drivers/power/avs/qcom-cpr.c:1957 cpr_fuse_match_tuple() warn: always true condition '(drv->cpr_fuse_map_match != (-1)) => (0-u16max != (-1))'
>
> vim +1957 drivers/power/avs/qcom-cpr.c
>
Rob,

Thanks for your precious comments! I will fix these issue soon after holiday.

Regards,
Jun
diff mbox series

Patch

diff --git a/drivers/power/avs/qcom-cpr.c b/drivers/power/avs/qcom-cpr.c
index bd7c3e48b386..1d9989636fa5 100644
--- a/drivers/power/avs/qcom-cpr.c
+++ b/drivers/power/avs/qcom-cpr.c
@@ -122,7 +122,7 @@ 
 
 #define CPR_FUSE_MIN_QUOT_DIFF		50
 
-#define FUSE_REVISION_UNKNOWN		(-1)
+#define FUSE_MATCH_ANY			(-1)
 
 enum voltage_change_dir {
 	NO_CHANGE,
@@ -150,6 +150,7 @@  struct fuse_corner_data {
 	/* fuse quot_offset */
 	int quot_offset_scale;
 	int quot_offset_adjust;
+	u32 min_diff_quot;
 };
 
 struct cpr_fuses {
@@ -165,7 +166,6 @@  struct corner_data {
 
 struct cpr_desc {
 	unsigned int num_fuse_corners;
-	int min_diff_quot;
 	int *step_quot;
 
 	unsigned int		timer_delay_us;
@@ -182,6 +182,7 @@  struct cpr_desc {
 	struct cpr_fuses cpr_fuses;
 	bool reduce_to_fuse_uV;
 	bool reduce_to_corner_uV;
+	unsigned int *mx_level; /* memory voltage level */
 };
 
 struct acc_desc {
@@ -207,6 +208,7 @@  struct fuse_corner {
 	const struct reg_sequence *accs;
 	int num_accs;
 	unsigned long max_freq;
+	unsigned int mx_level;
 	u8 ring_osc_idx;
 };
 
@@ -247,6 +249,8 @@  struct cpr_drv {
 	const struct cpr_fuse *cpr_fuses;
 
 	struct dentry *debugfs;
+	u16			cpr_fuse_map_count;
+	u16			cpr_fuse_map_match;
 };
 
 static bool cpr_is_allowed(struct cpr_drv *drv)
@@ -411,6 +415,10 @@  static int cpr_pre_voltage(struct cpr_drv *drv,
 {
 	struct fuse_corner *prev_fuse_corner = drv->corner->fuse_corner;
 
+	if (drv->desc->mx_level && dir == UP)
+		dev_pm_genpd_set_performance_state(drv->dev,
+						   fuse_corner->mx_level);
+
 	if (drv->tcsr && dir == DOWN)
 		cpr_set_acc(drv->tcsr, prev_fuse_corner, fuse_corner);
 
@@ -426,6 +434,10 @@  static int cpr_post_voltage(struct cpr_drv *drv,
 	if (drv->tcsr && dir == UP)
 		cpr_set_acc(drv->tcsr, prev_fuse_corner, fuse_corner);
 
+	if (drv->desc->mx_level && dir == DOWN)
+		dev_pm_genpd_set_performance_state(drv->dev,
+						   fuse_corner->mx_level);
+
 	return 0;
 }
 
@@ -439,7 +451,7 @@  static int cpr_scale_voltage(struct cpr_drv *drv, struct corner *corner,
 	if (ret)
 		return ret;
 
-	ret = regulator_set_voltage(drv->vdd_apc, new_uV, new_uV);
+	ret = regulator_set_voltage(drv->vdd_apc, new_uV, fuse_corner->max_uV);
 	if (ret) {
 		dev_err_ratelimited(drv->dev, "failed to set apc voltage %d\n",
 				    new_uV);
@@ -884,6 +896,48 @@  static int cpr_read_fuse_uV(const struct cpr_desc *desc,
 	return DIV_ROUND_UP(uV, step_volt) * step_volt;
 }
 
+static int cpr_corner_get_adjust(struct cpr_drv *drv, char *pname,
+				 unsigned int num_corners, u16 array_num,
+				 u32 **adjust, u32 *pp_valid)
+{
+	struct property *pp;
+	int i, len, index, size;
+	u32 *ptr;
+
+	*pp_valid = 0;
+	pp = of_find_property(drv->dev->of_node, pname, &len);
+	if (!pp)
+		return 0;
+
+	size = num_corners * array_num * sizeof(u32);
+	if (len != size) {
+		dev_err(drv->dev, "prop %s length %d is invalid, %d expected\n",
+			pname, len, size);
+		return -EINVAL;
+	}
+
+	/* use 1st vector if there is only one property array */
+	if (array_num == 1)
+		index = 0;
+	else
+		index = drv->cpr_fuse_map_match * num_corners;
+
+	*adjust = kzalloc(sizeof(u32) * num_corners,
+			GFP_KERNEL);
+	if (!*adjust)
+		return -ENOMEM;
+
+	ptr = *adjust;
+	for (i = 0; i < num_corners; i++, ptr++) {
+		of_property_read_u32_index(drv->dev->of_node,
+				pname, index + i, ptr);
+		dev_dbg(drv->dev, "use %s[%d]: %d\n", pname, i, *ptr);
+	}
+	*pp_valid = 1;
+
+	return 0;
+}
+
 static int cpr_fuse_corner_init(struct cpr_drv *drv)
 {
 	const struct cpr_desc *desc = drv->desc;
@@ -893,16 +947,35 @@  static int cpr_fuse_corner_init(struct cpr_drv *drv)
 	unsigned int step_volt;
 	struct fuse_corner_data *fdata;
 	struct fuse_corner *fuse, *end;
-	int uV;
+	int uV, *mx_level;
 	const struct reg_sequence *accs;
 	int ret;
+	char *quot_pp_name = "quotient-adjustment";
+	char *int_volt_pp_name = "init-voltage-adjustment";
+	u32 *quot_adjust, quot_pp_valid;
+	u32 *init_volt_adjust, volt_pp_valid;
 
 	accs = acc_desc->settings;
+	mx_level = desc->mx_level;
 
 	step_volt = regulator_get_linear_step(drv->vdd_apc);
 	if (!step_volt)
 		return -EINVAL;
 
+	ret = cpr_corner_get_adjust(drv, quot_pp_name,
+				    desc->num_fuse_corners,
+				    drv->cpr_fuse_map_count,
+				    &quot_adjust, &quot_pp_valid);
+	if (ret)
+		return ret;
+
+	ret = cpr_corner_get_adjust(drv, int_volt_pp_name,
+				    desc->num_fuse_corners,
+				    drv->cpr_fuse_map_count,
+				    &init_volt_adjust, &volt_pp_valid);
+	if (ret)
+		goto free_quot_pp;
+
 	/* Populate fuse_corner members */
 	fuse = drv->fuse_corners;
 	end = &fuse[desc->num_fuse_corners - 1];
@@ -920,9 +993,13 @@  static int cpr_fuse_corner_init(struct cpr_drv *drv)
 		/* Populate uV */
 		uV = cpr_read_fuse_uV(desc, fdata, fuses->init_voltage,
 				      step_volt, drv);
-		if (uV < 0)
-			return uV;
+		if (uV < 0) {
+			ret = uV;
+			goto free_quot_pp;
+		}
 
+		if (volt_pp_valid)
+			uV += init_volt_adjust[i];
 		fuse->min_uV = fdata->min_uV;
 		fuse->max_uV = fdata->max_uV;
 		fuse->uV = clamp(uV, fuse->min_uV, fuse->max_uV);
@@ -940,17 +1017,24 @@  static int cpr_fuse_corner_init(struct cpr_drv *drv)
 		/* Populate target quotient by scaling */
 		ret = cpr_read_efuse(drv->dev, fuses->quotient, &fuse->quot);
 		if (ret)
-			return ret;
+			goto free_volt_adjust;
 
 		fuse->quot *= fdata->quot_scale;
 		fuse->quot += fdata->quot_offset;
-		fuse->quot += fdata->quot_adjust;
+		if (quot_pp_valid)
+			fuse->quot += quot_adjust[i];
+		else
+			fuse->quot += fdata->quot_adjust;
 		fuse->step_quot = desc->step_quot[fuse->ring_osc_idx];
 
 		/* Populate acc settings */
 		fuse->accs = accs;
 		fuse->num_accs = acc_desc->num_regs_per_fuse;
 		accs += acc_desc->num_regs_per_fuse;
+
+		/* Populate memory power supply settings */
+		if (mx_level)
+			fuse->mx_level = mx_level[i];
 	}
 
 	/*
@@ -970,7 +1054,8 @@  static int cpr_fuse_corner_init(struct cpr_drv *drv)
 			dev_err(drv->dev,
 				"min uV: %d (fuse corner: %d) not supported by regulator\n",
 				fuse->min_uV, i);
-			return -EINVAL;
+			ret = -EINVAL;
+			goto free_volt_adjust;
 		}
 
 		ret = regulator_is_supported_voltage(drv->vdd_apc,
@@ -980,8 +1065,10 @@  static int cpr_fuse_corner_init(struct cpr_drv *drv)
 			dev_err(drv->dev,
 				"max uV: %d (fuse corner: %d) not supported by regulator\n",
 				fuse->max_uV, i);
-			return -EINVAL;
+			ret = -EINVAL;
+			goto free_quot_pp;
 		}
+		ret = 0;
 
 		dev_dbg(drv->dev,
 			"fuse corner %d: [%d %d %d] RO%hhu quot %d squot %d\n",
@@ -989,7 +1076,13 @@  static int cpr_fuse_corner_init(struct cpr_drv *drv)
 			fuse->ring_osc_idx, fuse->quot, fuse->step_quot);
 	}
 
-	return 0;
+free_volt_adjust:
+	if (volt_pp_valid)
+		kfree(init_volt_adjust);
+free_quot_pp:
+	if (quot_pp_valid)
+		kfree(quot_adjust);
+	return ret;
 }
 
 static int cpr_calculate_scaling(const char *quot_offset,
@@ -1006,7 +1099,7 @@  static int cpr_calculate_scaling(const char *quot_offset,
 	fuse = corner->fuse_corner;
 	prev_fuse = fuse - 1;
 
-	if (quot_offset) {
+	if (quot_offset && fdata->quot_offset_scale) {
 		ret = cpr_read_efuse(drv->dev, quot_offset, &quot_diff);
 		if (ret)
 			return ret;
@@ -1015,6 +1108,7 @@  static int cpr_calculate_scaling(const char *quot_offset,
 		quot_diff += fdata->quot_offset_adjust;
 	} else {
 		quot_diff = fuse->quot - prev_fuse->quot;
+		quot_diff = max(quot_diff, fdata->min_diff_quot);
 	}
 
 	freq_diff = fuse->max_freq - prev_fuse->max_freq;
@@ -1031,6 +1125,9 @@  static int cpr_interpolate(const struct corner *corner, int step_volt,
 	u64 temp, temp_limit;
 	const struct fuse_corner *fuse, *prev_fuse;
 
+	if (!fdata->max_volt_scale)
+		return corner->uV;
+
 	fuse = corner->fuse_corner;
 	prev_fuse = fuse - 1;
 
@@ -1085,6 +1182,7 @@  static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
 	struct device_node *desc_np;
 	struct device_node *child_np = NULL;
 	struct device_node *child_req_np = NULL;
+	int ret;
 
 	desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev);
 	if (!desc_np)
@@ -1094,6 +1192,14 @@  static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
 	if (!ref_np)
 		goto out_ref;
 
+	/* freq in cpr_opp is only used to calculate virtual corner quot, may
+	 * not identical with freq in cpu_opp.
+	 */
+	ret = of_property_read_u64(ref_np, "opp-hz", &rate);
+	if (!ret)
+		goto hz_done;
+
+	/* Support dtb that does not fill freq to cpr_opp */
 	do {
 		of_node_put(child_req_np);
 		child_np = of_get_next_available_child(desc_np, child_np);
@@ -1105,6 +1211,7 @@  static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref,
 
 	of_node_put(child_req_np);
 	of_node_put(child_np);
+hz_done:
 	of_node_put(ref_np);
 out_ref:
 	of_node_put(desc_np);
@@ -1116,7 +1223,7 @@  static int cpr_corner_init(struct cpr_drv *drv)
 {
 	const struct cpr_desc *desc = drv->desc;
 	const struct cpr_fuse *fuses = drv->cpr_fuses;
-	int i, level, scaling = 0;
+	int ret, i, level, scaling = 0;
 	unsigned int fnum, fc;
 	const char *quot_offset;
 	struct fuse_corner *fuse, *prev_fuse;
@@ -1128,6 +1235,10 @@  static int cpr_corner_init(struct cpr_drv *drv)
 	unsigned long freq;
 	int step_volt = regulator_get_linear_step(drv->vdd_apc);
 	struct dev_pm_opp *opp;
+	u32 *vc_min_uV, vc_min_uV_valid;
+	u32 *vc_quot_adjust, vc_quot_pp_valid;
+	char *quot_pp_name = "virtual-corner-quotient-adjustment";
+	char *min_uV_pp_name = "voltage-floor-override";
 
 	if (!step_volt)
 		return -EINVAL;
@@ -1141,14 +1252,30 @@  static int cpr_corner_init(struct cpr_drv *drv)
 	if (!cdata)
 		return -ENOMEM;
 
+	ret = cpr_corner_get_adjust(drv, quot_pp_name,
+				    drv->num_corners, 1,
+				    &vc_quot_adjust, &vc_quot_pp_valid);
+	if (ret)
+		goto free_cdata;
+
+	ret = cpr_corner_get_adjust(drv, min_uV_pp_name,
+				    drv->num_corners, 1,
+				    &vc_min_uV, &vc_min_uV_valid);
+	if (ret)
+		goto free_vc_quot_pp;
+
 	/*
 	 * Store maximum frequency for each fuse corner based on the frequency
 	 * plan
 	 */
 	for (level = 1; level <= drv->num_corners; level++) {
 		opp = dev_pm_opp_find_level_exact(&drv->pd.dev, level);
-		if (IS_ERR(opp))
-			return -EINVAL;
+		if (IS_ERR(opp)) {
+			ret = -EINVAL;
+			dev_err(drv->dev, "Fail to find cpu opp for level %d\n",
+				level);
+			goto free_vc_min_uV_pp;
+		}
 		fc = cpr_get_fuse_corner(opp);
 		if (!fc) {
 			dev_pm_opp_put(opp);
@@ -1158,8 +1285,10 @@  static int cpr_corner_init(struct cpr_drv *drv)
 		freq = cpr_get_opp_hz_for_req(opp, drv->attached_cpu_dev);
 		if (!freq) {
 			dev_pm_opp_put(opp);
-			return -EINVAL;
+			ret = -EINVAL;
+			goto free_vc_min_uV_pp;
 		}
+
 		cdata[level - 1].fuse_corner = fnum;
 		cdata[level - 1].freq = freq;
 
@@ -1227,8 +1356,10 @@  static int cpr_corner_init(struct cpr_drv *drv)
 		if (prev_fuse && cdata[i - 1].freq == prev_fuse->max_freq) {
 			scaling = cpr_calculate_scaling(quot_offset, drv,
 							fdata, corner);
-			if (scaling < 0)
-				return scaling;
+			if (scaling < 0) {
+				ret = scaling;
+				goto free_vc_min_uV_pp;
+			}
 
 			apply_scaling = true;
 		} else if (corner->freq == fuse->max_freq) {
@@ -1243,9 +1374,15 @@  static int cpr_corner_init(struct cpr_drv *drv)
 
 			corner->uV = cpr_interpolate(corner, step_volt, fdata);
 		}
+		if (vc_quot_pp_valid)
+			corner->quot_adjust -= vc_quot_adjust[i];
+
+		if (vc_min_uV_valid)
+			corner->min_uV = max(fuse->min_uV, (int)vc_min_uV[i]);
+		else
+			corner->min_uV = fuse->min_uV;
 
 		corner->max_uV = fuse->max_uV;
-		corner->min_uV = fuse->min_uV;
 		corner->uV = clamp(corner->uV, corner->min_uV, corner->max_uV);
 		corner->last_uV = corner->uV;
 
@@ -1260,7 +1397,15 @@  static int cpr_corner_init(struct cpr_drv *drv)
 			fuse->quot - corner->quot_adjust);
 	}
 
-	return 0;
+free_vc_min_uV_pp:
+	if (vc_min_uV_valid)
+		kfree(vc_min_uV);
+free_vc_quot_pp:
+	if (vc_quot_pp_valid)
+		kfree(vc_quot_adjust);
+free_cdata:
+	devm_kfree(drv->dev, cdata);
+	return ret;
 }
 
 static const struct cpr_fuse *cpr_get_fuses(struct cpr_drv *drv)
@@ -1294,6 +1439,9 @@  static const struct cpr_fuse *cpr_get_fuses(struct cpr_drv *drv)
 		if (!fuses[i].quotient)
 			return ERR_PTR(-ENOMEM);
 
+		if (!desc->cpr_fuses.fuse_corner_data[i].quot_offset_scale)
+			continue;
+
 		snprintf(tbuf, 32, "cpr_quotient_offset%d", i + 1);
 		fuses[i].quotient_offset = devm_kstrdup(drv->dev, tbuf,
 							GFP_KERNEL);
@@ -1385,7 +1533,6 @@  static int cpr_find_initial_corner(struct cpr_drv *drv)
 
 static const struct cpr_desc qcs404_cpr_desc = {
 	.num_fuse_corners = 3,
-	.min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF,
 	.step_quot = (int []){ 25, 25, 25, },
 	.timer_delay_us = 5000,
 	.timer_cons_up = 0,
@@ -1396,6 +1543,7 @@  static const struct cpr_desc qcs404_cpr_desc = {
 	.gcnt_us = 1,
 	.vdd_apc_step_up_limit = 1,
 	.vdd_apc_step_down_limit = 1,
+	.mx_level = NULL,
 	.cpr_fuses = {
 		.init_voltage_step = 8000,
 		.init_voltage_width = 6,
@@ -1412,6 +1560,7 @@  static const struct cpr_desc qcs404_cpr_desc = {
 				.quot_adjust = 0,
 				.quot_offset_scale = 5,
 				.quot_offset_adjust = 0,
+				.min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF,
 			},
 			/* fuse corner 1 */
 			{
@@ -1425,6 +1574,7 @@  static const struct cpr_desc qcs404_cpr_desc = {
 				.quot_adjust = -20,
 				.quot_offset_scale = 5,
 				.quot_offset_adjust = 0,
+				.min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF,
 			},
 			/* fuse corner 2 */
 			{
@@ -1438,6 +1588,7 @@  static const struct cpr_desc qcs404_cpr_desc = {
 				.quot_adjust = 0,
 				.quot_offset_scale = 5,
 				.quot_offset_adjust = 0,
+				.min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF,
 			},
 		},
 	},
@@ -1464,6 +1615,86 @@  static const struct cpr_acc_desc qcs404_cpr_acc_desc = {
 	.acc_desc = &qcs404_acc_desc,
 };
 
+static const struct cpr_desc msm8939_cpr_desc = {
+	.num_fuse_corners = 3,
+	.step_quot = (int []){ 13, 13, 13, },
+	.timer_delay_us = 5000,
+	.timer_cons_up = 0,
+	.timer_cons_down = 2,
+	.up_threshold = 0,
+	.down_threshold = 8,
+	.idle_clocks = 15,
+	.gcnt_us = 1,
+	.vdd_apc_step_up_limit = 1,
+	.vdd_apc_step_down_limit = 1,
+	.mx_level = (unsigned int[]){3, 4, 6},
+	.cpr_fuses = {
+		.init_voltage_step = 10000,
+		.init_voltage_width = 6,
+		.fuse_corner_data = (struct fuse_corner_data[]){
+			/* fuse corner 0 */
+			{
+				.ref_uV = 1050000,
+				.max_uV = 1050000,
+				.min_uV = 1050000,
+				.max_volt_scale = 0,
+				.max_quot_scale = 0,
+				.quot_offset = 0,
+				.quot_scale = 1,
+				.quot_adjust = 0,
+				.quot_offset_scale = 0,
+				.quot_offset_adjust = 0,
+				.min_diff_quot = 0,
+			},
+			/* fuse corner 1 */
+			{
+				.ref_uV = 1150000,
+				.max_uV = 1150000,
+				.min_uV = 1050000,
+				.max_volt_scale = 0,
+				.max_quot_scale = 0,
+				.quot_offset = 0,
+				.quot_scale = 1,
+				.quot_adjust = -41,
+				.quot_offset_scale = 0,
+				.quot_offset_adjust = 0,
+				.min_diff_quot = 0,
+			},
+			/* fuse corner 2 */
+			{
+				.ref_uV = 1350000,
+				.max_uV = 1350000,
+				.min_uV = 1137500,
+				.max_volt_scale = 0,
+				.max_quot_scale = 900,
+				.quot_offset = 0,
+				.quot_scale = 1,
+				.quot_adjust = 0,
+				.quot_offset_scale = 0,
+				.quot_offset_adjust = 0,
+				.min_diff_quot = 40,
+			},
+		},
+	},
+};
+
+static const struct acc_desc msm8939_acc_desc = {
+	.settings = (struct reg_sequence[]){
+		{ 0xb130, 0},
+		{ 0xb124, 0},
+		{ 0xb130, 0},
+		{ 0xb124, 0},
+		{ 0xb130, 1},
+		{ 0xb124, 0x3000},
+	},
+	.num_regs_per_fuse = 2,
+};
+
+static const struct cpr_acc_desc msm8939_cpr_acc_desc = {
+	.cpr_desc = &msm8939_cpr_desc,
+	.acc_desc = &msm8939_acc_desc,
+};
+
 static unsigned int cpr_get_performance_state(struct generic_pm_domain *genpd,
 					      struct dev_pm_opp *opp)
 {
@@ -1540,6 +1771,7 @@  static int cpr_pd_attach_dev(struct generic_pm_domain *domain,
 		goto unlock;
 	}
 	drv->num_corners = ret;
+	dev_dbg(drv->dev, "has %d virtual corners.\n", ret);
 
 	if (drv->num_corners < 2) {
 		dev_err(drv->dev, "need at least 2 OPPs to use CPR\n");
@@ -1651,6 +1883,94 @@  static void cpr_debugfs_init(struct cpr_drv *drv)
 			    drv, &cpr_debug_info_fops);
 }
 
+/* Match with map table to find out entry index that to retrieve adjustment
+ * array from properties lookup table.
+ */
+#define FUSE_MATCH_TUPLE_SIZE 3
+static int cpr_fuse_match_tuple(struct device *dev, struct cpr_drv *drv)
+{
+	u32 cpr_rev, cpr_rev_high;
+	u32 pvs_ver, pvs_ver_high;
+	struct property *pp;
+	int i, tuple_size = FUSE_MATCH_TUPLE_SIZE;
+	int ret, len;
+	u32 *tmp, speedbin;
+
+	drv->cpr_fuse_map_match = FUSE_MATCH_ANY;
+	ret = cpr_read_efuse(dev, "cpr_fuse_revision", &cpr_rev);
+	if (ret)
+		return ret;
+
+	ret = cpr_read_efuse(dev, "cpr_fuse_revision_high", &cpr_rev_high);
+	if (!ret)
+		cpr_rev |= cpr_rev_high << 1;
+
+	dev_info(dev, "fuse revision %d\n", cpr_rev);
+
+	pp = of_find_property(dev->of_node, "fuse-version-map", &len);
+	if (!pp)
+		return 0;
+
+	ret = cpr_read_efuse(dev, "cpr_pvs_version", &pvs_ver);
+	if (ret)
+		return ret;
+
+	ret = cpr_read_efuse(dev, "cpr_pvs_version_high", &pvs_ver_high);
+	if (!ret)
+		pvs_ver |= pvs_ver_high << 1;
+
+	ret = cpr_read_efuse(dev, "cpr_speedbin", &speedbin);
+
+	dev_info(dev, "pvs_version %d, speed_bin %d\n", pvs_ver, speedbin);
+
+	drv->cpr_fuse_map_count = len / (sizeof(u32) * tuple_size);
+
+	if (len == 0 || len % (sizeof(u32) * tuple_size)) {
+		dev_err(dev, "fuse-version-map length=%d is invalid\n",
+			len);
+		return -EINVAL;
+	}
+
+	tmp = kzalloc(len, GFP_KERNEL);
+	if (!tmp)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_array(dev->of_node, "fuse-version-map",
+				tmp, drv->cpr_fuse_map_count * tuple_size);
+	if (ret) {
+		dev_err(dev, "could not read fuse-version-map, rc=%d\n",
+			ret);
+		return ret;
+	}
+
+	/*
+	 * fuse-version-map tuple format:
+	 * <speed_bin, pvs_version, cpr_fuse_revision
+	 */
+	for (i = 0; i < drv->cpr_fuse_map_count; i++) {
+		if (tmp[i * tuple_size] != speedbin
+		    && tmp[i * tuple_size] != FUSE_MATCH_ANY)
+			continue;
+		if (tmp[i * tuple_size + 1] != pvs_ver
+		    && tmp[i * tuple_size + 1] != FUSE_MATCH_ANY)
+			continue;
+		if (tmp[i * tuple_size + 2] != cpr_rev
+		    && tmp[i * tuple_size + 2] != FUSE_MATCH_ANY)
+			continue;
+		drv->cpr_fuse_map_match = i;
+		break;
+	}
+	if (drv->cpr_fuse_map_match != FUSE_MATCH_ANY) {
+		dev_info(dev, "fuse map tuple match found: %d\n",
+			drv->cpr_fuse_map_match);
+	} else {
+		dev_warn(dev, "fuse map tuple match not found\n");
+		drv->cpr_fuse_map_match = 0;
+		drv->cpr_fuse_map_count = 1;
+	}
+	return 0;
+}
+
 static int cpr_probe(struct platform_device *pdev)
 {
 	struct resource *res;
@@ -1659,7 +1979,6 @@  static int cpr_probe(struct platform_device *pdev)
 	int irq, ret;
 	const struct cpr_acc_desc *data;
 	struct device_node *np;
-	u32 cpr_rev = FUSE_REVISION_UNKNOWN;
 
 	data = of_device_get_match_data(dev);
 	if (!data || !data->cpr_desc || !data->acc_desc)
@@ -1707,7 +2026,7 @@  static int cpr_probe(struct platform_device *pdev)
 	 * initialized after attaching to the power domain,
 	 * since it depends on the CPU's OPP table.
 	 */
-	ret = cpr_read_efuse(dev, "cpr_fuse_revision", &cpr_rev);
+	ret = cpr_fuse_match_tuple(dev, drv);
 	if (ret)
 		return ret;
 
@@ -1776,6 +2095,7 @@  static int cpr_remove(struct platform_device *pdev)
 
 static const struct of_device_id cpr_match_table[] = {
 	{ .compatible = "qcom,qcs404-cpr", .data = &qcs404_cpr_acc_desc },
+	{ .compatible = "qcom,msm8939-cpr", .data = &msm8939_cpr_acc_desc },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, cpr_match_table);