Merge "msm: cpr-regulator: calculate quotient adjustment dynamically"
diff --git a/Documentation/devicetree/bindings/arm/msm/cpr-regulator.txt b/Documentation/devicetree/bindings/arm/msm/cpr-regulator.txt
index 1b881f0..aa24dc6 100644
--- a/Documentation/devicetree/bindings/arm/msm/cpr-regulator.txt
+++ b/Documentation/devicetree/bindings/arm/msm/cpr-regulator.txt
@@ -203,21 +203,6 @@
- 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-quot-adjust-table: Array of triples in which each triple indicates the speed bin of the CPU, the virtual
- corner to use and the quotient adjustment.
- The 3 elements in one triple are:
- [0]: => the speed bin of the CPU.
- [1]: => the virtual voltage corner to use.
- [2]: => the quotient adjustment for the corresponding virtual corner.
- If the speed bin in a triple is equal to the speed bin of the CPU, the adjustment would
- be subtracted from the quotient value of the voltage corner when the CPU is running at
- that virtual corner. Each virtual corner value must be in the range 1 to the number of
- elements in qcom,cpr-corner-map.
-- 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 is required if qcom,cpr-quot-adjust-table is present.
- qcom,cpr-quotient-adjustment: Array of three elements of CPR quotient adjustments for each corner.
The 3 quotient adjustments with index[0..2] are:
[0] => amount to add to the SVS quotient
@@ -230,6 +215,42 @@
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 quintuples in which each quintuple maps a CPU speed bin and PVS version to
+ the maximum virtual voltage corner corresponding to the SVS, NORMAL and TURBO corners.
+ The 5 elements in one quintuple are:
+ [0]: => the speed bin of the CPU.
+ [1]: => the PVS version of the CPU.
+ [2]: => the max virtual voltage corner value corresponding to SVS corner for this speed bin.
+ [3]: => the max virtual voltage corner value corresponding to NORMAL corner for this speed bin.
+ [4]: => the max virtual voltage corner value corresponding to TURBO corner for this speed bin.
+ 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 quintuples in this property. If the property is
+ specified, then quotient scaling is enabled for the TURBO 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.
+ This property is required if qcom,cpr-speed-bin-max-corners is present.
+- qcom,cpr-quot-adjust-scaling-factor-max: The maximum allowed CPR target quotient scaling factor to use when
+ calculating the quotient adjustment for a given virtual voltage corner. It
+ corresponds to 'scaling' in this equation:
+ quot_adjust = (freq_turbo - freq_corner) * scaling / 1000.
+ This property is required if qcom,cpr-speed-bin-max-corners is present.
Example:
apc_vreg_corner: regulator@f9018000 {
@@ -319,9 +340,25 @@
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-quot-adjust-table = <1 1 0>, <1 2 0>, <1 3 0>,
- <1 4 0>, <1 5 450>, <1 6 375>,
- <1 7 300>, <1 8 225>, <1 9 187>,
- <1 10 150>, <1 11 75>, <1 12 0>;
+ 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-quot-adjust-scaling-factor-max = <650>;
};
diff --git a/arch/arm/boot/dts/msm8226-v2.dtsi b/arch/arm/boot/dts/msm8226-v2.dtsi
index 6215740..14fe237 100644
--- a/arch/arm/boot/dts/msm8226-v2.dtsi
+++ b/arch/arm/boot/dts/msm8226-v2.dtsi
@@ -37,14 +37,28 @@
qcom,cpr-up-threshold = <0>;
qcom,cpr-down-threshold = <5>;
qcom,cpr-corner-map = <1 1 2 2 3 3 3 3 3 3 3 3 3 3>;
- qcom,cpr-quot-adjust-table =
- <1 5 450>,
- <1 6 375>,
- <1 7 300>,
- <1 8 225>,
- <1 9 187>,
- <1 10 150>,
- <1 11 75>;
+ qcom,pvs-version-fuse-sel = <22 4 2 0>;
+ 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>,
+ <13 1689600000>,
+ <14 1785600000>;
+ qcom,cpr-speed-bin-max-corners =
+ <0 2 2 4 7>,
+ <1 2 2 4 12>,
+ <2 2 2 4 10>,
+ <5 2 2 4 14>;
+ qcom,cpr-quot-adjust-scaling-factor-max = <650>;
};
&msm_gpu {
diff --git a/arch/arm/boot/dts/msm8926.dtsi b/arch/arm/boot/dts/msm8926.dtsi
index 8a0e5c4..e866286 100644
--- a/arch/arm/boot/dts/msm8926.dtsi
+++ b/arch/arm/boot/dts/msm8926.dtsi
@@ -138,15 +138,29 @@
regulator-min-microvolt = <1>;
regulator-max-microvolt = <14>;
qcom,cpr-corner-map = <1 1 2 2 3 3 3 3 3 3 3 3 3 3>;
- qcom,cpr-quot-adjust-table =
- <1 5 450>,
- <1 6 375>,
- <1 7 300>,
- <1 8 225>,
- <1 9 187>,
- <1 10 150>,
- <1 11 75>;
qcom,cpr-quotient-adjustment = <0 72 72>;
+ qcom,pvs-version-fuse-sel = <22 4 2 0>;
+ 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>,
+ <13 1689600000>,
+ <14 1785600000>;
+ 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-quot-adjust-scaling-factor-max = <650>;
};
&tsens {
diff --git a/arch/arm/mach-msm/cpr-regulator.c b/arch/arm/mach-msm/cpr-regulator.c
index 039d2d7..d952f82 100644
--- a/arch/arm/mach-msm/cpr-regulator.c
+++ b/arch/arm/mach-msm/cpr-regulator.c
@@ -169,6 +169,8 @@
/* Process voltage variables */
u32 pvs_bin;
u32 speed_bin;
+ u32 pvs_version;
+
/* APC voltage regulator */
struct regulator *vdd_apc;
@@ -1291,14 +1293,48 @@
return rc;
}
-static int cpr_get_of_corner_mappings(struct cpr_regulator *cpr_vreg,
+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);
+ pr_info("[row: %d]: 0x%llx, pvs_version = %d\n",
+ fuse_sel[0], fuse_bits, cpr_vreg->pvs_version);
+ } else {
+ cpr_vreg->pvs_version = UINT_MAX;
+ }
+}
+
+/*
+ * cpr_get_corner_quot_adjustment() -- get the quot_adjust for each corner.
+ *
+ * Get the corner to fuse corner (SVS/NORMAL/TURBO) mappings and corner to
+ * APC clock frequency mappings from device tree.
+ * Calculate the quotient adjustment scaling factor for those corners mapping
+ * to the TURBO fuse corner.
+ * Calculate the quotient adjustment for each corner which map to the TURBO
+ * fuse corner.
+ */
+static int cpr_get_corner_quot_adjustment(struct cpr_regulator *cpr_vreg,
struct device *dev)
{
int rc = 0;
- int i, size, stripe_size;
+ int i, size;
struct property *prop;
- u32 *tmp;
bool corners_mapped;
+ u32 *tmp, *freq_mappings = NULL;
+ u32 scaling, max_factor;
+ u32 corner, turbo_corner = 0, normal_corner = 0, svs_corner = 0;
+ u32 freq_turbo, freq_normal, freq_corner;
prop = of_find_property(dev->of_node, "qcom,cpr-corner-map", NULL);
@@ -1313,81 +1349,182 @@
cpr_vreg->corner_map = devm_kzalloc(dev, sizeof(int) * (size + 1),
GFP_KERNEL);
if (!cpr_vreg->corner_map) {
- pr_err("Can't allocate cpr_vreg->corner_map memory\n");
+ pr_err("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) {
+ pr_err("Can't allocate memory for cpr_vreg->quot_adjust\n");
+ return -ENOMEM;
+ }
+
if (!corners_mapped) {
for (i = CPR_FUSE_CORNER_SVS; i < CPR_FUSE_CORNER_MAX; i++)
cpr_vreg->corner_map[i] = i;
+ return 0;
} else {
rc = of_property_read_u32_array(dev->of_node,
"qcom,cpr-corner-map", &cpr_vreg->corner_map[1], size);
if (rc) {
- pr_err("qcom,cpr-corner-map missing, rc = %d", rc);
+ pr_err("qcom,cpr-corner-map missing, rc = %d\n", rc);
return rc;
}
}
- cpr_vreg->quot_adjust = devm_kzalloc(dev,
- sizeof(int) * (cpr_vreg->num_corners + 1),
- GFP_KERNEL);
- if (!cpr_vreg->quot_adjust) {
- pr_err("Can't allocate cpr_vreg->quot_adjust memory\n");
+ prop = of_find_property(dev->of_node,
+ "qcom,cpr-speed-bin-max-corners", NULL);
+ if (!prop) {
+ cpr_debug("qcom,cpr-speed-bin-max-corner missing\n");
+ return 0;
+ }
+
+ size = prop->length / sizeof(u32);
+ tmp = kzalloc(size * sizeof(u32), GFP_KERNEL);
+ if (!tmp) {
+ pr_err("memory alloc failed\n");
return -ENOMEM;
}
-
- prop = of_find_property(dev->of_node, "qcom,cpr-quot-adjust-table",
- NULL);
-
- if (prop) {
- if (!corners_mapped) {
- pr_err("qcom,cpr-corner-map missing\n");
- return -EINVAL;
- }
-
- size = prop->length / sizeof(u32);
- tmp = kzalloc(sizeof(u32) * size, GFP_KERNEL);
- if (!tmp)
- return -ENOMEM;
-
- rc = of_property_read_u32_array(dev->of_node,
- "qcom,cpr-quot-adjust-table", tmp, size);
- if (rc) {
- pr_err("qcom,cpr-quot-adjust-table missing, rc = %d",
- rc);
- kfree(tmp);
- return rc;
- }
-
- stripe_size = sizeof(struct quot_adjust_info) / sizeof(int);
-
- if ((size % stripe_size) != 0) {
- pr_err("qcom,cpr-quot-adjust-table data is not correct");
- kfree(tmp);
- return -EINVAL;
- }
-
- for (i = 0; i < size; i += stripe_size) {
- if (tmp[i] == cpr_vreg->speed_bin) {
- if (tmp[i + 1] >= 1 &&
- tmp[i + 1] <=
- cpr_vreg->num_corners) {
- cpr_vreg->quot_adjust[tmp[i + 1]] =
- tmp[i + 2];
- } else {
- pr_err("qcom,cpr-quot-adjust-table data is not correct");
- kfree(tmp);
- return -EINVAL;
- }
- }
- }
-
+ rc = of_property_read_u32_array(dev->of_node,
+ "qcom,cpr-speed-bin-max-corners", tmp, size);
+ if (rc < 0) {
kfree(tmp);
+ pr_err("get cpr-speed-bin-max-corners failed, rc = %d\n", rc);
+ return rc;
}
+ cpr_parse_pvs_version_fuse(cpr_vreg, dev->of_node);
+
+ /*
+ * According to speed_bin && pvs_version, get the maximum
+ * corner corresponding to SVS/NORMAL/TURBO fuse corner.
+ */
+ for (i = 0; i < size; i += 5) {
+ if (tmp[i] == cpr_vreg->speed_bin &&
+ tmp[i + 1] == cpr_vreg->pvs_version) {
+ svs_corner = tmp[i + 2];
+ normal_corner = tmp[i + 3];
+ turbo_corner = tmp[i + 4];
+ break;
+ }
+ }
+ kfree(tmp);
+ /*
+ * Return success if the virtual corner values read from
+ * qcom,cpr-speed-bin-max-corners property are incorrect,
+ * which make sure the driver could continue run without
+ * error.
+ */
+ if (turbo_corner <= normal_corner ||
+ turbo_corner > cpr_vreg->num_corners) {
+ cpr_debug("turbo:%d should be larger than normal:%d\n",
+ turbo_corner, normal_corner);
+ return 0;
+ }
+
+ prop = of_find_property(dev->of_node,
+ "qcom,cpr-corner-frequency-map", NULL);
+ if (!prop) {
+ cpr_debug("qcom,cpr-corner-frequency-map missing\n");
+ return 0;
+ }
+
+ size = prop->length / sizeof(u32);
+ tmp = kzalloc(sizeof(u32) * size, GFP_KERNEL);
+ if (!tmp) {
+ pr_err("memory alloc failed\n");
+ return -ENOMEM;
+ }
+ rc = of_property_read_u32_array(dev->of_node,
+ "qcom,cpr-corner-frequency-map", tmp, size);
+ if (rc < 0) {
+ pr_err("get cpr-corner-frequency-map failed, rc = %d\n", rc);
+ kfree(tmp);
+ return rc;
+ }
+ freq_mappings = kzalloc(sizeof(u32) * (cpr_vreg->num_corners + 1),
+ GFP_KERNEL);
+ if (!freq_mappings) {
+ pr_err("memory alloc for freq_mappings failed!\n");
+ kfree(tmp);
+ return -ENOMEM;
+ }
+ for (i = 0; i < size; i += 2) {
+ corner = tmp[i];
+ if ((corner < 1) || (corner > cpr_vreg->num_corners)) {
+ pr_err("corner should be in 1~%d range: %d\n",
+ cpr_vreg->num_corners, corner);
+ continue;
+ }
+ freq_mappings[corner] = tmp[i + 1];
+ cpr_debug("Frequency at virtual corner %d is %d Hz.\n",
+ corner, freq_mappings[corner]);
+ }
+ kfree(tmp);
+
+ rc = of_property_read_u32(dev->of_node,
+ "qcom,cpr-quot-adjust-scaling-factor-max",
+ &max_factor);
+ if (rc < 0) {
+ cpr_debug("get cpr-quot-adjust-scaling-factor-max failed\n");
+ kfree(freq_mappings);
+ return 0;
+ }
+
+ /*
+ * Get the quot adjust scaling factor, according to:
+ * scaling =
+ * min(1000 * (QUOT(fused @turbo) - QUOT(fused @normal)) /
+ * (freq_turbo - freq_normal), max_factor)
+ *
+ * @QUOT(fused @turbo): quotient read from fuse for TURBO fuse corner;
+ * @QUOT(fused @normal): quotient read from fuse for NORMAL fuse corner;
+ * @freq_turbo: MHz, max frequency running at TURBO fuse corner;
+ * @freq_normal: MHz, max frequency running at NORMAL fuse corner.
+ */
+
+ freq_turbo = freq_mappings[turbo_corner];
+ freq_normal = freq_mappings[normal_corner];
+ if (freq_normal == 0 || freq_turbo <= freq_normal) {
+ pr_err("freq_turbo: %d should larger than freq_normal: %d\n",
+ freq_turbo, freq_normal);
+ kfree(freq_mappings);
+ return -EINVAL;
+ }
+ freq_turbo /= 1000000; /* MHz */
+ freq_normal /= 1000000;
+ scaling = 1000 *
+ (cpr_vreg->cpr_fuse_target_quot[CPR_FUSE_CORNER_TURBO] -
+ cpr_vreg->cpr_fuse_target_quot[CPR_FUSE_CORNER_NORMAL]) /
+ (freq_turbo - freq_normal);
+ scaling = min(scaling, max_factor);
+ pr_info("quotient adjustment scaling factor: %d.%03d\n",
+ scaling / 1000, scaling % 1000);
+
+ /*
+ * Walk through the corners mapped to the TURBO fuse corner and
+ * calculate the quotient adjustment for each one using the following
+ * formula:
+ * quot_adjust = (freq_turbo - freq_corner) * scaling / 1000
+ *
+ * @freq_turbo: MHz, max frequency running at TURBO fuse corner;
+ * @freq_corner: MHz, frequency running at a corner.
+ */
+ for (i = turbo_corner; i > normal_corner; i--) {
+ freq_corner = freq_mappings[i] / 1000000; /* MHz */
+ if (freq_corner > 0) {
+ cpr_vreg->quot_adjust[i] =
+ scaling * (freq_turbo - freq_corner) / 1000;
+ }
+ pr_info("adjusted quotient[%d] = %d\n", i,
+ (cpr_vreg->cpr_fuse_target_quot[cpr_vreg->corner_map[i]]
+ - cpr_vreg->quot_adjust[i]));
+ }
+ kfree(freq_mappings);
return 0;
}
@@ -1531,7 +1668,7 @@
}
}
- rc = cpr_get_of_corner_mappings(cpr_vreg, &pdev->dev);
+ rc = cpr_get_corner_quot_adjustment(cpr_vreg, &pdev->dev);
if (rc)
return rc;