feat(stpmic1): add new services

Add support for ICC, sink mode, bypass mode,
active discharge and list voltages.
Handle LDO3 sink source mode in a different way to avoid
setting voltage while in sink source mode.

Change-Id: Ib1b909fd8a153f542917f650e43e24317a570534
Signed-off-by: Pascal Paillet <p.paillet@st.com>
diff --git a/drivers/st/pmic/stpmic1.c b/drivers/st/pmic/stpmic1.c
index 714d532..37eb50b 100644
--- a/drivers/st/pmic/stpmic1.c
+++ b/drivers/st/pmic/stpmic1.c
@@ -23,10 +23,17 @@
 	uint8_t pull_down;
 	uint8_t mask_reset_reg;
 	uint8_t mask_reset;
+	uint8_t icc_reg;
+	uint8_t icc_mask;
 };
 
 static struct i2c_handle_s *pmic_i2c_handle;
 static uint16_t pmic_i2c_addr;
+/*
+ * Special mode corresponds to LDO3 in sink source mode or in bypass mode.
+ * LDO3 doesn't switch back from special to normal mode.
+ */
+static bool ldo3_special_mode;
 
 /* Voltage tables in mV */
 static const uint16_t buck1_voltage_table[] = {
@@ -347,10 +354,13 @@
 	3300,
 	3300,
 	3300,
-	500,
-	0xFFFF, /* VREFDDR */
 };
 
+/* Special mode table is used for sink source OR bypass mode */
+static const uint16_t ldo3_special_mode_table[] = {
+	0,
+};
+
 static const uint16_t ldo5_voltage_table[] = {
 	1700,
 	1700,
@@ -438,6 +448,8 @@
 		.pull_down	= BUCK1_PULL_DOWN_SHIFT,
 		.mask_reset_reg	= MASK_RESET_BUCK_REG,
 		.mask_reset	= BUCK1_MASK_RESET,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= BUCK1_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "buck2",
@@ -450,6 +462,8 @@
 		.pull_down	= BUCK2_PULL_DOWN_SHIFT,
 		.mask_reset_reg	= MASK_RESET_BUCK_REG,
 		.mask_reset	= BUCK2_MASK_RESET,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= BUCK2_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "buck3",
@@ -462,6 +476,8 @@
 		.pull_down	= BUCK3_PULL_DOWN_SHIFT,
 		.mask_reset_reg	= MASK_RESET_BUCK_REG,
 		.mask_reset	= BUCK3_MASK_RESET,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= BUCK3_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "buck4",
@@ -474,6 +490,8 @@
 		.pull_down	= BUCK4_PULL_DOWN_SHIFT,
 		.mask_reset_reg	= MASK_RESET_BUCK_REG,
 		.mask_reset	= BUCK4_MASK_RESET,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= BUCK4_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "ldo1",
@@ -484,6 +502,8 @@
 		.low_power_reg	= LDO1_PWRCTRL_REG,
 		.mask_reset_reg	= MASK_RESET_LDO_REG,
 		.mask_reset	= LDO1_MASK_RESET,
+		.icc_reg	= LDO_ICC_TURNOFF_REG,
+		.icc_mask	= LDO1_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "ldo2",
@@ -494,6 +514,8 @@
 		.low_power_reg	= LDO2_PWRCTRL_REG,
 		.mask_reset_reg	= MASK_RESET_LDO_REG,
 		.mask_reset	= LDO2_MASK_RESET,
+		.icc_reg	= LDO_ICC_TURNOFF_REG,
+		.icc_mask	= LDO2_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "ldo3",
@@ -504,6 +526,8 @@
 		.low_power_reg	= LDO3_PWRCTRL_REG,
 		.mask_reset_reg	= MASK_RESET_LDO_REG,
 		.mask_reset	= LDO3_MASK_RESET,
+		.icc_reg	= LDO_ICC_TURNOFF_REG,
+		.icc_mask	= LDO3_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "ldo4",
@@ -514,6 +538,8 @@
 		.low_power_reg	= LDO4_PWRCTRL_REG,
 		.mask_reset_reg	= MASK_RESET_LDO_REG,
 		.mask_reset	= LDO4_MASK_RESET,
+		.icc_reg	= LDO_ICC_TURNOFF_REG,
+		.icc_mask	= LDO4_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "ldo5",
@@ -524,6 +550,8 @@
 		.low_power_reg	= LDO5_PWRCTRL_REG,
 		.mask_reset_reg	= MASK_RESET_LDO_REG,
 		.mask_reset	= LDO5_MASK_RESET,
+		.icc_reg	= LDO_ICC_TURNOFF_REG,
+		.icc_mask	= LDO5_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "ldo6",
@@ -534,6 +562,8 @@
 		.low_power_reg	= LDO6_PWRCTRL_REG,
 		.mask_reset_reg	= MASK_RESET_LDO_REG,
 		.mask_reset	= LDO6_MASK_RESET,
+		.icc_reg	= LDO_ICC_TURNOFF_REG,
+		.icc_mask	= LDO6_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "vref_ddr",
@@ -551,6 +581,8 @@
 		.voltage_table_size = ARRAY_SIZE(fixed_5v_voltage_table),
 		.control_reg	= USB_CONTROL_REG,
 		.enable_mask	= BOOST_ENABLED,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= BOOST_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "pwr_sw1",
@@ -558,6 +590,8 @@
 		.voltage_table_size = ARRAY_SIZE(fixed_5v_voltage_table),
 		.control_reg	= USB_CONTROL_REG,
 		.enable_mask	= USBSW_OTG_SWITCH_ENABLED,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= PWR_SW1_ICC_SHIFT,
 	},
 	{
 		.dt_node_name	= "pwr_sw2",
@@ -565,6 +599,8 @@
 		.voltage_table_size = ARRAY_SIZE(fixed_5v_voltage_table),
 		.control_reg	= USB_CONTROL_REG,
 		.enable_mask	= SWIN_SWOUT_ENABLED,
+		.icc_reg	= BUCK_ICC_TURNOFF_REG,
+		.icc_mask	= PWR_SW2_ICC_SHIFT,
 	},
 };
 
@@ -649,11 +685,21 @@
 	const struct regul_struct *regul = get_regulator_data(name);
 	uint8_t mask;
 
+	if ((strncmp(name, "ldo3", 5) == 0) && ldo3_special_mode) {
+		/*
+		 * when the LDO3 is in special mode, we do not change voltage,
+		 * because by setting voltage, the LDO would leaves sink-source
+		 * mode. There is obviously no reason to leave sink-source mode
+		 * at runtime.
+		 */
+		return 0;
+	}
+
 	/* Voltage can be set for buck<N> or ldo<N> (except ldo4) regulators */
 	if (strncmp(name, "buck", 4) == 0) {
 		mask = BUCK_VOLTAGE_MASK;
 	} else if ((strncmp(name, "ldo", 3) == 0) &&
-		   (strncmp(name, "ldo4", 4) != 0)) {
+		   (strncmp(name, "ldo4", 5) != 0)) {
 		mask = LDO_VOLTAGE_MASK;
 	} else {
 		return 0;
@@ -682,12 +728,90 @@
 {
 	const struct regul_struct *regul = get_regulator_data(name);
 
+	if (regul->mask_reset_reg == 0U) {
+		return -EPERM;
+	}
+
 	return stpmic1_register_update(regul->mask_reset_reg,
 				       BIT(regul->mask_reset),
 				       LDO_BUCK_RESET_MASK <<
 				       regul->mask_reset);
 }
 
+int stpmic1_regulator_icc_set(const char *name)
+{
+	const struct regul_struct *regul = get_regulator_data(name);
+
+	if (regul->mask_reset_reg == 0U) {
+		return -EPERM;
+	}
+
+	return stpmic1_register_update(regul->icc_reg,
+				       BIT(regul->icc_mask),
+				       BIT(regul->icc_mask));
+}
+
+int stpmic1_regulator_sink_mode_set(const char *name)
+{
+	if (strncmp(name, "ldo3", 5) != 0) {
+		return -EPERM;
+	}
+
+	ldo3_special_mode = true;
+
+	/* disable bypass mode, enable sink mode */
+	return stpmic1_register_update(LDO3_CONTROL_REG,
+				       LDO3_DDR_SEL << LDO_BUCK_VOLTAGE_SHIFT,
+				       LDO3_BYPASS | LDO_VOLTAGE_MASK);
+}
+
+int stpmic1_regulator_bypass_mode_set(const char *name)
+{
+	if (strncmp(name, "ldo3", 5) != 0) {
+		return -EPERM;
+	}
+
+	ldo3_special_mode = true;
+
+	/* enable bypass mode, disable sink mode */
+	return stpmic1_register_update(LDO3_CONTROL_REG,
+				       LDO3_BYPASS,
+				       LDO3_BYPASS | LDO_VOLTAGE_MASK);
+}
+
+int stpmic1_active_discharge_mode_set(const char *name)
+{
+	if (strncmp(name, "pwr_sw1", 8) == 0) {
+		return stpmic1_register_update(USB_CONTROL_REG,
+					       VBUS_OTG_DISCHARGE,
+					       VBUS_OTG_DISCHARGE);
+	}
+
+	if (strncmp(name, "pwr_sw2", 8) == 0) {
+		return stpmic1_register_update(USB_CONTROL_REG,
+					       SW_OUT_DISCHARGE,
+					       SW_OUT_DISCHARGE);
+	}
+
+	return -EPERM;
+}
+
+int stpmic1_regulator_levels_mv(const char *name, const uint16_t **levels,
+				size_t *levels_count)
+{
+	const struct regul_struct *regul = get_regulator_data(name);
+
+	if ((strncmp(name, "ldo3", 5) == 0) && ldo3_special_mode) {
+		*levels_count = ARRAY_SIZE(ldo3_special_mode_table);
+		*levels = ldo3_special_mode_table;
+	} else {
+		*levels_count = regul->voltage_table_size;
+		*levels = regul->voltage_table;
+	}
+
+	return 0;
+}
+
 int stpmic1_regulator_voltage_get(const char *name)
 {
 	const struct regul_struct *regul = get_regulator_data(name);
@@ -695,11 +819,15 @@
 	uint8_t mask;
 	int status;
 
+	if ((strncmp(name, "ldo3", 5) == 0) && ldo3_special_mode) {
+		return 0;
+	}
+
 	/* Voltage can be set for buck<N> or ldo<N> (except ldo4) regulators */
 	if (strncmp(name, "buck", 4) == 0) {
 		mask = BUCK_VOLTAGE_MASK;
 	} else if ((strncmp(name, "ldo", 3) == 0) &&
-		   (strncmp(name, "ldo4", 4) != 0)) {
+		   (strncmp(name, "ldo4", 5) != 0)) {
 		mask = LDO_VOLTAGE_MASK;
 	} else {
 		return 0;
diff --git a/include/drivers/st/stpmic1.h b/include/drivers/st/stpmic1.h
index a69e5b5..2dfc7f8 100644
--- a/include/drivers/st/stpmic1.h
+++ b/include/drivers/st/stpmic1.h
@@ -103,6 +103,22 @@
 #define BUCK4_PULL_DOWN_SHIFT		6
 #define VREF_DDR_PULL_DOWN_SHIFT	4
 
+/* ICC register */
+#define BUCK1_ICC_SHIFT			0
+#define BUCK2_ICC_SHIFT			1
+#define BUCK3_ICC_SHIFT			2
+#define BUCK4_ICC_SHIFT			3
+#define PWR_SW1_ICC_SHIFT		4
+#define PWR_SW2_ICC_SHIFT		5
+#define BOOST_ICC_SHIFT			6
+
+#define LDO1_ICC_SHIFT			0
+#define LDO2_ICC_SHIFT			1
+#define LDO3_ICC_SHIFT			2
+#define LDO4_ICC_SHIFT			3
+#define LDO5_ICC_SHIFT			4
+#define LDO6_ICC_SHIFT			5
+
 /* Buck Mask reset register */
 #define BUCK1_MASK_RESET		0
 #define BUCK2_MASK_RESET		1
@@ -118,6 +134,10 @@
 #define LDO6_MASK_RESET			5
 #define VREF_DDR_MASK_RESET		6
 
+/* LDO3 Special modes */
+#define LDO3_BYPASS                     BIT(7)
+#define LDO3_DDR_SEL                    31U
+
 /* Main PMIC Control Register (MAIN_CONTROL_REG) */
 #define ICC_EVENT_ENABLED		BIT(4)
 #define PWRCTRL_POLARITY_HIGH		BIT(3)
@@ -145,6 +165,8 @@
 /* USB Control Register */
 #define BOOST_OVP_DISABLED		BIT(7)
 #define VBUS_OTG_DETECTION_DISABLED	BIT(6)
+#define SW_OUT_DISCHARGE		BIT(5)
+#define VBUS_OTG_DISCHARGE		BIT(4)
 #define OCP_LIMIT_HIGH			BIT(3)
 #define SWIN_SWOUT_ENABLED		BIT(2)
 #define USBSW_OTG_SWITCH_ENABLED	BIT(1)
@@ -159,9 +181,15 @@
 int stpmic1_regulator_disable(const char *name);
 bool stpmic1_is_regulator_enabled(const char *name);
 int stpmic1_regulator_voltage_set(const char *name, uint16_t millivolts);
+int stpmic1_regulator_levels_mv(const char *name, const uint16_t **levels,
+				size_t *levels_count);
 int stpmic1_regulator_voltage_get(const char *name);
 int stpmic1_regulator_pull_down_set(const char *name);
 int stpmic1_regulator_mask_reset_set(const char *name);
+int stpmic1_regulator_icc_set(const char *name);
+int stpmic1_regulator_sink_mode_set(const char *name);
+int stpmic1_regulator_bypass_mode_set(const char *name);
+int stpmic1_active_discharge_mode_set(const char *name);
 void stpmic1_bind_i2c(struct i2c_handle_s *i2c_handle, uint16_t i2c_addr);
 
 int stpmic1_get_version(unsigned long *version);