regulator: pm8xxx-regulator: Add control for boost regulators

Add the functions required to control boost regulators on PMIC
PM8XXX chips.  These regulators would typically be used to output
5.0 V for USB OTG but may be programmed for voltages between
4.0 V and 5.55 V.

Change-Id: I016b9ffa41853c18fda919048eb06002d9ca0c64
Signed-off-by: David Collins <collinsd@codeaurora.org>
diff --git a/drivers/regulator/pm8xxx-regulator.c b/drivers/regulator/pm8xxx-regulator.c
index 94b028d..fa17449 100644
--- a/drivers/regulator/pm8xxx-regulator.c
+++ b/drivers/regulator/pm8xxx-regulator.c
@@ -401,6 +401,19 @@
 #define NCP_SET_POINTS			((NCP_UV_MAX - NCP_UV_MIN) \
 						/ NCP_UV_STEP + 1)
 
+/* Boost masks and values */
+#define BOOST_ENABLE_MASK		0x80
+#define BOOST_DISABLE			0x00
+#define BOOST_ENABLE			0x80
+#define BOOST_VPROG_MASK		0x1F
+
+#define BOOST_UV_MIN			4000000
+#define BOOST_UV_MAX			5550000
+#define BOOST_UV_STEP			50000
+
+#define BOOST_SET_POINTS		((BOOST_UV_MAX - BOOST_UV_MIN) \
+						/ BOOST_UV_STEP + 1)
+
 #define vreg_err(vreg, fmt, ...) \
 	pr_err("%s: " fmt, vreg->rdesc.name, ##__VA_ARGS__)
 
@@ -1424,6 +1437,64 @@
 	return rc;
 }
 
+static int pm8xxx_boost_get_voltage(struct regulator_dev *rdev)
+{
+	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
+	u8 vprog;
+
+	vprog = vreg->ctrl_reg & BOOST_VPROG_MASK;
+
+	return BOOST_UV_STEP * vprog + BOOST_UV_MIN;
+}
+
+static int pm8xxx_boost_list_voltage(struct regulator_dev *rdev,
+				    unsigned selector)
+{
+	if (selector >= BOOST_SET_POINTS)
+		return 0;
+
+	return selector * BOOST_UV_STEP + BOOST_UV_MIN;
+}
+
+static int pm8xxx_boost_set_voltage(struct regulator_dev *rdev, int min_uV,
+				   int max_uV, unsigned *selector)
+{
+	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+	int uV = min_uV;
+	u8 val;
+
+	if (uV < BOOST_UV_MIN && max_uV >= BOOST_UV_MIN)
+		uV = BOOST_UV_MIN;
+
+	if (uV < BOOST_UV_MIN || uV > BOOST_UV_MAX) {
+		vreg_err(vreg,
+			"request v=[%d, %d] is outside possible v=[%d, %d]\n",
+			 min_uV, max_uV, BOOST_UV_MIN, BOOST_UV_MAX);
+		return -EINVAL;
+	}
+
+	val = (uV - BOOST_UV_MIN + BOOST_UV_STEP - 1) / BOOST_UV_STEP;
+	uV = val * BOOST_UV_STEP + BOOST_UV_MIN;
+
+	if (uV > max_uV) {
+		vreg_err(vreg,
+			"request v=[%d, %d] cannot be met by any set point\n",
+			min_uV, max_uV);
+		return -EINVAL;
+	}
+
+	/* voltage setting */
+	rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, val,
+			BOOST_VPROG_MASK, &vreg->ctrl_reg);
+	if (rc)
+		vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc);
+	else
+		pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_VOLTAGE);
+
+	return rc;
+}
+
 static unsigned int pm8xxx_ldo_get_mode(struct regulator_dev *rdev)
 {
 	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
@@ -2066,6 +2137,38 @@
 	return rc;
 }
 
+static int pm8xxx_boost_enable(struct regulator_dev *rdev)
+{
+	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, BOOST_ENABLE,
+		BOOST_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc);
+	else
+		pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_ENABLE);
+
+	return rc;
+}
+
+static int pm8xxx_boost_disable(struct regulator_dev *rdev)
+{
+	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
+	int rc;
+
+	rc = pm8xxx_vreg_masked_write(vreg, vreg->ctrl_addr, BOOST_DISABLE,
+		BOOST_ENABLE_MASK, &vreg->ctrl_reg);
+
+	if (rc)
+		vreg_err(vreg, "pm8xxx_vreg_masked_write failed, rc=%d\n", rc);
+	else
+		pm8xxx_vreg_show_state(rdev, PM8XXX_REGULATOR_ACTION_DISABLE);
+
+	return rc;
+}
+
 static int pm8xxx_ldo_pin_control_enable(struct regulator_dev *rdev)
 {
 	struct pm8xxx_vreg *vreg = rdev_get_drvdata(rdev);
@@ -2506,6 +2609,11 @@
 		pr_info("%s %-9s: %s, v=%7d uV\n",
 			action_label, vreg->rdesc.name, enable_label, uV);
 		break;
+	case PM8XXX_REGULATOR_TYPE_BOOST:
+		uV = pm8xxx_boost_get_voltage(rdev);
+		pr_info("%s %-9s: %s, v=%7d uV\n",
+			action_label, vreg->rdesc.name, enable_label, uV);
+		break;
 	default:
 		break;
 	}
@@ -2601,6 +2709,16 @@
 	.enable_time		= pm8xxx_enable_time,
 };
 
+static struct regulator_ops pm8xxx_boost_ops = {
+	.enable			= pm8xxx_boost_enable,
+	.disable		= pm8xxx_boost_disable,
+	.is_enabled		= pm8xxx_vreg_is_enabled,
+	.set_voltage		= pm8xxx_boost_set_voltage,
+	.get_voltage		= pm8xxx_boost_get_voltage,
+	.list_voltage		= pm8xxx_boost_list_voltage,
+	.enable_time		= pm8xxx_enable_time,
+};
+
 /* Pin control regulator operations. */
 static struct regulator_ops pm8xxx_ldo_pc_ops = {
 	.enable			= pm8xxx_ldo_pin_control_enable,
@@ -2629,6 +2747,7 @@
 	[PM8XXX_REGULATOR_TYPE_VS]		= &pm8xxx_vs_ops,
 	[PM8XXX_REGULATOR_TYPE_VS300]		= &pm8xxx_vs300_ops,
 	[PM8XXX_REGULATOR_TYPE_NCP]		= &pm8xxx_ncp_ops,
+	[PM8XXX_REGULATOR_TYPE_BOOST]		= &pm8xxx_boost_ops,
 };
 
 static struct regulator_ops *pm8xxx_reg_pc_ops[PM8XXX_REGULATOR_TYPE_MAX] = {
@@ -2647,6 +2766,7 @@
 	[PM8XXX_REGULATOR_TYPE_VS]		= 0,
 	[PM8XXX_REGULATOR_TYPE_VS300]		= 0,
 	[PM8XXX_REGULATOR_TYPE_NCP]		= NCP_SET_POINTS,
+	[PM8XXX_REGULATOR_TYPE_BOOST]		= BOOST_SET_POINTS,
 };
 
 static int pm8xxx_init_ldo(struct pm8xxx_vreg *vreg, bool is_real)
@@ -2937,6 +3057,20 @@
 	return rc;
 }
 
+static int pm8xxx_init_boost(struct pm8xxx_vreg *vreg)
+{
+	int rc;
+
+	/* Save the current control register state. */
+	rc = pm8xxx_readb(vreg->dev->parent, vreg->ctrl_addr, &vreg->ctrl_reg);
+	if (rc) {
+		vreg_err(vreg, "pm8xxx_readb failed, rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
 static int __devinit pm8xxx_vreg_probe(struct platform_device *pdev)
 {
 	struct pm8xxx_regulator_core_platform_data *core_data;
@@ -3058,6 +3192,9 @@
 	case PM8XXX_REGULATOR_TYPE_NCP:
 		rc = pm8xxx_init_ncp(vreg);
 		break;
+	case PM8XXX_REGULATOR_TYPE_BOOST:
+		rc = pm8xxx_init_boost(vreg);
+		break;
 	default:
 		break;
 	}