[PATCH 4/5] regulator: s5m8767: Use GPIO for controlling Buck9/eMMC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




Add support for GPIO control (enable/disable) over Buck9. The Buck9
Converter is used as a supply for eMMC Host Controller.

BUCK9EN GPIO of S5M8767 chip may be used by application processor to
enable or disable the Buck9. This has two benefits:
 - It is faster than toggling it over I2C bus.
 - It allows disabling the regulator during suspend to RAM; The AP will
   enable it during resume; Without the patch the regulator supplying
   eMMC must be defined as fixed-regulator.

Signed-off-by: Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx>
Cc: Kyungmin Park <kyungmin.park@xxxxxxxxxxx>
Cc: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx>
---
 drivers/regulator/s5m8767.c         |  120 ++++++++++++++++++++++++++++++++++-
 include/linux/mfd/samsung/core.h    |    2 +
 include/linux/mfd/samsung/s5m8767.h |    7 ++
 3 files changed, 128 insertions(+), 1 deletion(-)

diff --git a/drivers/regulator/s5m8767.c b/drivers/regulator/s5m8767.c
index d7164bb75d3e..86fb44c56bac 100644
--- a/drivers/regulator/s5m8767.c
+++ b/drivers/regulator/s5m8767.c
@@ -48,6 +48,8 @@ struct s5m8767_info {
 	int buck_gpios[3];
 	int buck_ds[3];
 	int buck_gpioindex;
+	bool buck9_uses_gpio;
+	int buck9_gpio;
 };
 
 struct sec_voltage_desc {
@@ -261,6 +263,43 @@ static int s5m8767_reg_disable(struct regulator_dev *rdev)
 			S5M8767_ENCTRL_MASK, ~S5M8767_ENCTRL_MASK);
 }
 
+static int s5m8767_reg_gpio_is_enabled(struct regulator_dev *rdev)
+{
+	struct s5m8767_info *s5m8767 = rdev_get_drvdata(rdev);
+	int val;
+
+	if (!s5m8767->buck9_uses_gpio)
+		return s5m8767_reg_is_enabled(rdev);
+
+	val = gpio_get_value(s5m8767->buck9_gpio);
+
+	return val == 1;
+}
+
+static int s5m8767_reg_gpio_enable(struct regulator_dev *rdev)
+{
+	struct s5m8767_info *s5m8767 = rdev_get_drvdata(rdev);
+
+	if (!s5m8767->buck9_uses_gpio)
+		return s5m8767_reg_enable(rdev);
+
+	gpio_set_value(s5m8767->buck9_gpio, 1);
+
+	return 0;
+}
+
+static int s5m8767_reg_gpio_disable(struct regulator_dev *rdev)
+{
+	struct s5m8767_info *s5m8767 = rdev_get_drvdata(rdev);
+
+	if (!s5m8767->buck9_uses_gpio)
+		return s5m8767_reg_disable(rdev);
+
+	gpio_set_value(s5m8767->buck9_gpio, 0);
+
+	return 0;
+}
+
 static int s5m8767_get_vsel_reg(int reg_id, struct s5m8767_info *s5m8767)
 {
 	int reg;
@@ -427,6 +466,17 @@ static struct regulator_ops s5m8767_buck78_ops = {
 	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
 };
 
+static struct regulator_ops s5m8767_buck_gpio_ops = {
+	.list_voltage		= regulator_list_voltage_linear,
+	.is_enabled		= s5m8767_reg_gpio_is_enabled,
+	.enable			= s5m8767_reg_gpio_enable,
+	.disable		= s5m8767_reg_gpio_disable,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
+	.set_voltage_time_sel	= s5m8767_set_voltage_time_sel,
+};
+
+
 #define s5m8767_regulator_desc(_name) {		\
 	.name		= #_name,		\
 	.id		= S5M8767_##_name,	\
@@ -443,6 +493,14 @@ static struct regulator_ops s5m8767_buck78_ops = {
 	.owner		= THIS_MODULE,		\
 }
 
+#define s5m8767_regulator_gpio_desc(_name) {	\
+	.name		= #_name,		\
+	.id		= S5M8767_##_name,	\
+	.ops		= &s5m8767_buck_gpio_ops,	\
+	.type		= REGULATOR_VOLTAGE,	\
+	.owner		= THIS_MODULE,		\
+}
+
 static struct regulator_desc regulators[] = {
 	s5m8767_regulator_desc(LDO1),
 	s5m8767_regulator_desc(LDO2),
@@ -480,9 +538,50 @@ static struct regulator_desc regulators[] = {
 	s5m8767_regulator_desc(BUCK6),
 	s5m8767_regulator_buck78_desc(BUCK7),
 	s5m8767_regulator_buck78_desc(BUCK8),
-	s5m8767_regulator_desc(BUCK9),
+	s5m8767_regulator_gpio_desc(BUCK9),
 };
 
+/*
+ * Initialize BUCK9EN GPIO and BUCK9 control register.
+ * Assuming DT or platform data is already parsed and buck9_uses_gpio is true.
+ */
+static int s5m8767_init_buck9en_gpio(struct s5m8767_info *s5m8767)
+{
+	int i, ret, mode = 0;
+	unsigned int data;
+
+	/* Check if opmode for Buck9 matches pmic-buck9-uses-gpio */
+	for (i = 0; i < s5m8767->num_regulators; i++) {
+		const struct sec_opmode_data *opmode = &s5m8767->opmode[i];
+		if (opmode->id == S5M8767_BUCK9) {
+			mode = s5m8767_opmode_reg[S5M8767_BUCK9][opmode->mode];
+			break;
+		}
+	}
+	if (mode != S5M8767_ENCTRL_USE_GPIO) {
+		dev_err(s5m8767->dev,
+				"Mismatched op_mode (%x) and uses-gpio for Buck9, fallback to normal mode\n",
+				mode);
+		s5m8767->buck9_uses_gpio = false;
+		return 0;
+	}
+
+	if (!gpio_is_valid(s5m8767->buck9_gpio)) {
+		dev_err(s5m8767->dev, "Buck9 GPIO not valid\n");
+		return -EINVAL;
+	}
+
+	ret = devm_gpio_request_one(s5m8767->dev, s5m8767->buck9_gpio,
+			GPIOF_OUT_INIT_HIGH, "S5M8767 BUCK9EN");
+	if (ret)
+		return ret;
+
+	data = S5M8767_ENCTRL_USE_GPIO << S5M8767_ENCTRL_SHIFT;
+
+	return regmap_update_bits(s5m8767->iodev->regmap_pmic,
+			S5M8767_REG_BUCK9CTRL1, S5M8767_ENCTRL_MASK, data);
+}
+
 #ifdef CONFIG_OF
 static int s5m8767_pmic_dt_parse_dvs_gpio(struct sec_pmic_dev *iodev,
 			struct sec_platform_data *pdata,
@@ -663,6 +762,17 @@ static int s5m8767_pmic_dt_parse_pdata(struct platform_device *pdev,
 			pdata->buck_ramp_delay = 0;
 	}
 
+	if (of_get_property(pmic_np, "s5m8767,pmic-buck9-uses-gpio", NULL)) {
+		int gpio = of_get_named_gpio(pmic_np,
+					"s5m8767,pmic-buck9-gpio", 0);
+		if (!gpio_is_valid(gpio)) {
+			dev_err(iodev->dev, "invalid buck9 gpio: %d\n", gpio);
+			return -EINVAL;
+		}
+		pdata->buck9_uses_gpio = true;
+		pdata->buck9_gpio = gpio;
+	}
+
 	return 0;
 }
 #else
@@ -740,6 +850,8 @@ static int s5m8767_pmic_probe(struct platform_device *pdev)
 	s5m8767->buck_ds[0] = pdata->buck_ds[0];
 	s5m8767->buck_ds[1] = pdata->buck_ds[1];
 	s5m8767->buck_ds[2] = pdata->buck_ds[2];
+	s5m8767->buck9_uses_gpio = pdata->buck9_uses_gpio;
+	s5m8767->buck9_gpio = pdata->buck9_gpio;
 
 	s5m8767->ramp_delay = pdata->buck_ramp_delay;
 	s5m8767->buck2_ramp = pdata->buck2_ramp_enable;
@@ -917,6 +1029,12 @@ static int s5m8767_pmic_probe(struct platform_device *pdev)
 					val << S5M8767_DVS_BUCK_RAMP_SHIFT);
 	}
 
+	if (s5m8767->buck9_uses_gpio) {
+		ret = s5m8767_init_buck9en_gpio(s5m8767);
+		if (ret)
+			return ret;
+	}
+
 	for (i = 0; i < pdata->num_regulators; i++) {
 		const struct sec_voltage_desc *desc;
 		int id = pdata->regulators[i].id;
diff --git a/include/linux/mfd/samsung/core.h b/include/linux/mfd/samsung/core.h
index 41c9bde410c5..8c52ecea3f11 100644
--- a/include/linux/mfd/samsung/core.h
+++ b/include/linux/mfd/samsung/core.h
@@ -80,6 +80,8 @@ struct sec_platform_data {
 	bool				buck3_gpiodvs;
 	unsigned int			buck4_voltage[8];
 	bool				buck4_gpiodvs;
+	int				buck9_gpio;
+	bool				buck9_uses_gpio;
 
 	int				buck_set1;
 	int				buck_set2;
diff --git a/include/linux/mfd/samsung/s5m8767.h b/include/linux/mfd/samsung/s5m8767.h
index 2ab0b0f03641..d383c46a9ad8 100644
--- a/include/linux/mfd/samsung/s5m8767.h
+++ b/include/linux/mfd/samsung/s5m8767.h
@@ -183,8 +183,15 @@ enum s5m8767_regulators {
 	S5M8767_REG_MAX,
 };
 
+/* LDO_EN/BUCK_EN field in registers */
 #define S5M8767_ENCTRL_SHIFT		6
 #define S5M8767_ENCTRL_MASK		(0x3 << S5M8767_ENCTRL_SHIFT)
+/*
+ * LDO_EN/BUCK_EN register value for controlling this Buck or LDO
+ * by GPIO (PWREN, BUCKEN).
+ */
+#define S5M8767_ENCTRL_USE_GPIO		0x1
+
 
 /*
  * Values for BUCK_RAMP field in DVS_RAMP register, matching raw values
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux