Merge "msm: rpm-regulator-smd: Add support for voltage floor corner parameter"
diff --git a/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt b/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt
index d930799..61a9235 100644
--- a/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt
+++ b/Documentation/devicetree/bindings/arm/msm/rpm-regulator-smd.txt
@@ -62,6 +62,16 @@
 				1.  This value shift is necessary to work around
 				limitations in the regulator framework which
 				treat 0 uV as an error.
+- qcom,use-voltage-floor-corner:  Flag that signifies if regulator_set_voltage
+				calls should modify the floor corner parameter
+				instead of the voltage parameter.  When used,
+				voltages specified inside of the regulator
+				framework represent corners that have been
+				incremented by 1.  The properties
+				qcom,use-voltage-corner and
+				qcom,use-voltage-floor-corner are mutually
+				exclusive.  Only one may be specified for a
+				given regulator.
 The following properties specify initial values for parameters to be sent to the
 RPM in regulator requests.
 - qcom,init-enable:            0 = regulator disabled
@@ -165,6 +175,10 @@
 					0 = Allow RPM to utilize LDO bypass mode
 						if possible
 					1 = Disallow LDO bypass mode
+- qcom,init-voltage-floor-corner:  Minimum performance corner to use if any
+				processor in the system is awake.  This property
+				supports the same values as
+				qcom,init-voltage-corner.
 
 All properties specified within the core regulator framework can also be used in
 second level nodes.  These bindings can be found in:
diff --git a/arch/arm/mach-msm/rpm-regulator-smd.c b/arch/arm/mach-msm/rpm-regulator-smd.c
index 8c96b1a..d9573c4 100644
--- a/arch/arm/mach-msm/rpm-regulator-smd.c
+++ b/arch/arm/mach-msm/rpm-regulator-smd.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2013, 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
@@ -69,6 +69,7 @@
 	RPM_REGULATOR_PARAM_FREQ_REASON,
 	RPM_REGULATOR_PARAM_CORNER,
 	RPM_REGULATOR_PARAM_BYPASS,
+	RPM_REGULATOR_PARAM_FLOOR_CORNER,
 	RPM_REGULATOR_PARAM_MAX,
 };
 
@@ -114,6 +115,7 @@
 	PARAM(FREQ_REASON,     0,  1,  0,  1, "resn", 0, 8,          "qcom,init-freq-reason"),
 	PARAM(CORNER,          1,  1,  0,  0, "corn", 0, 6,          "qcom,init-voltage-corner"),
 	PARAM(BYPASS,          1,  0,  0,  0, "bypa", 0, 1,          "qcom,init-disallow-bypass"),
+	PARAM(FLOOR_CORNER,    1,  1,  0,  0, "vfc",  0, 6,          "qcom,init-voltage-floor-corner"),
 };
 
 struct rpm_vreg_request {
@@ -443,6 +445,7 @@
 	RPM_VREG_AGGR_MAX(FREQ_REASON, param_aggr, param_reg);
 	RPM_VREG_AGGR_MAX(CORNER, param_aggr, param_reg);
 	RPM_VREG_AGGR_MAX(BYPASS, param_aggr, param_reg);
+	RPM_VREG_AGGR_MAX(FLOOR_CORNER, param_aggr, param_reg);
 }
 
 static int rpm_vreg_aggregate_requests(struct rpm_regulator *regulator)
@@ -709,6 +712,56 @@
 		+ RPM_REGULATOR_CORNER_NONE;
 }
 
+static int rpm_vreg_set_voltage_floor_corner(struct regulator_dev *rdev,
+				int min_uV, int max_uV, unsigned *selector)
+{
+	struct rpm_regulator *reg = rdev_get_drvdata(rdev);
+	int rc = 0;
+	int corner;
+	u32 prev_corner;
+
+	/*
+	 * Translate from values which work as inputs in the
+	 * regulator_set_voltage function to the actual corner values
+	 * sent to the RPM.
+	 */
+	corner = min_uV - RPM_REGULATOR_CORNER_NONE;
+
+	if (corner < params[RPM_REGULATOR_PARAM_FLOOR_CORNER].min
+	    || corner > params[RPM_REGULATOR_PARAM_FLOOR_CORNER].max) {
+		vreg_err(reg, "corner=%d is not within allowed range: [%u, %u]\n",
+			corner, params[RPM_REGULATOR_PARAM_FLOOR_CORNER].min,
+			params[RPM_REGULATOR_PARAM_FLOOR_CORNER].max);
+		return -EINVAL;
+	}
+
+	rpm_vreg_lock(reg->rpm_vreg);
+
+	prev_corner = reg->req.param[RPM_REGULATOR_PARAM_FLOOR_CORNER];
+	RPM_VREG_SET_PARAM(reg, FLOOR_CORNER, corner);
+
+	/* Only send a new voltage if the regulator is currently enabled. */
+	if (rpm_vreg_active_or_sleep_enabled(reg->rpm_vreg))
+		rc = rpm_vreg_aggregate_requests(reg);
+
+	if (rc) {
+		vreg_err(reg, "set voltage corner failed, rc=%d", rc);
+		RPM_VREG_SET_PARAM(reg, FLOOR_CORNER, prev_corner);
+	}
+
+	rpm_vreg_unlock(reg->rpm_vreg);
+
+	return rc;
+}
+
+static int rpm_vreg_get_voltage_floor_corner(struct regulator_dev *rdev)
+{
+	struct rpm_regulator *reg = rdev_get_drvdata(rdev);
+
+	return reg->req.param[RPM_REGULATOR_PARAM_FLOOR_CORNER]
+		+ RPM_REGULATOR_CORNER_NONE;
+}
+
 static int rpm_vreg_set_mode(struct regulator_dev *rdev, unsigned int mode)
 {
 	struct rpm_regulator *reg = rdev_get_drvdata(rdev);
@@ -1035,6 +1088,18 @@
 	.enable_time		= rpm_vreg_enable_time,
 };
 
+static struct regulator_ops ldo_floor_corner_ops = {
+	.enable			= rpm_vreg_enable,
+	.disable		= rpm_vreg_disable,
+	.is_enabled		= rpm_vreg_is_enabled,
+	.set_voltage		= rpm_vreg_set_voltage_floor_corner,
+	.get_voltage		= rpm_vreg_get_voltage_floor_corner,
+	.set_mode		= rpm_vreg_set_mode,
+	.get_mode		= rpm_vreg_get_mode,
+	.get_optimum_mode	= rpm_vreg_get_optimum_mode,
+	.enable_time		= rpm_vreg_enable_time,
+};
+
 static struct regulator_ops smps_ops = {
 	.enable			= rpm_vreg_enable,
 	.disable		= rpm_vreg_disable,
@@ -1059,6 +1124,18 @@
 	.enable_time		= rpm_vreg_enable_time,
 };
 
+static struct regulator_ops smps_floor_corner_ops = {
+	.enable			= rpm_vreg_enable,
+	.disable		= rpm_vreg_disable,
+	.is_enabled		= rpm_vreg_is_enabled,
+	.set_voltage		= rpm_vreg_set_voltage_floor_corner,
+	.get_voltage		= rpm_vreg_get_voltage_floor_corner,
+	.set_mode		= rpm_vreg_set_mode,
+	.get_mode		= rpm_vreg_get_mode,
+	.get_optimum_mode	= rpm_vreg_get_optimum_mode,
+	.enable_time		= rpm_vreg_enable_time,
+};
+
 static struct regulator_ops switch_ops = {
 	.enable			= rpm_vreg_enable,
 	.disable		= rpm_vreg_disable,
@@ -1192,10 +1269,23 @@
 	 * is specified in the device node (SMPS and LDO only).
 	 */
 	if (of_property_read_bool(node, "qcom,use-voltage-corner")) {
+		if (of_property_read_bool(node,
+				"qcom,use-voltage-floor-corner")) {
+			dev_err(dev, "%s: invalid properties: both qcom,use-voltage-corner and qcom,use-voltage-floor-corner specified\n",
+				__func__);
+			goto fail_free_reg;
+		}
+
 		if (regulator_type == RPM_REGULATOR_SMD_TYPE_SMPS)
 			reg->rdesc.ops = &smps_corner_ops;
 		else if (regulator_type == RPM_REGULATOR_SMD_TYPE_LDO)
 			reg->rdesc.ops = &ldo_corner_ops;
+	} else if (of_property_read_bool(node,
+			"qcom,use-voltage-floor-corner")) {
+		if (regulator_type == RPM_REGULATOR_SMD_TYPE_SMPS)
+			reg->rdesc.ops = &smps_floor_corner_ops;
+		else if (regulator_type == RPM_REGULATOR_SMD_TYPE_LDO)
+			reg->rdesc.ops = &ldo_floor_corner_ops;
 	}
 
 	if (regulator_type == RPM_REGULATOR_SMD_TYPE_VS)