summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/regulator/tps65910-regulator.c206
-rw-r--r--include/linux/mfd/tps65910.h8
2 files changed, 214 insertions, 0 deletions
diff --git a/drivers/regulator/tps65910-regulator.c b/drivers/regulator/tps65910-regulator.c
index c9c58a2d8433..1a74d9ee79a9 100644
--- a/drivers/regulator/tps65910-regulator.c
+++ b/drivers/regulator/tps65910-regulator.c
@@ -26,6 +26,9 @@
#include <linux/mfd/tps65910.h>
#define TPS65910_SUPPLY_STATE_ENABLED 0x1
+#define EXT_SLEEP_CONTROL (TPS65910_SLEEP_CONTROL_EXT_INPUT_EN1 | \
+ TPS65910_SLEEP_CONTROL_EXT_INPUT_EN2 | \
+ TPS65910_SLEEP_CONTROL_EXT_INPUT_EN3)
/* supported VIO voltages in milivolts */
static const u16 VIO_VSEL_table[] = {
@@ -252,6 +255,39 @@ static struct tps_info tps65911_regs[] = {
},
};
+#define EXT_CONTROL_REG_BITS(id, regs_offs, bits) (((regs_offs) << 8) | (bits))
+static unsigned int tps65910_ext_sleep_control[] = {
+ 0,
+ EXT_CONTROL_REG_BITS(VIO, 1, 0),
+ EXT_CONTROL_REG_BITS(VDD1, 1, 1),
+ EXT_CONTROL_REG_BITS(VDD2, 1, 2),
+ EXT_CONTROL_REG_BITS(VDD3, 1, 3),
+ EXT_CONTROL_REG_BITS(VDIG1, 0, 1),
+ EXT_CONTROL_REG_BITS(VDIG2, 0, 2),
+ EXT_CONTROL_REG_BITS(VPLL, 0, 6),
+ EXT_CONTROL_REG_BITS(VDAC, 0, 7),
+ EXT_CONTROL_REG_BITS(VAUX1, 0, 3),
+ EXT_CONTROL_REG_BITS(VAUX2, 0, 4),
+ EXT_CONTROL_REG_BITS(VAUX33, 0, 5),
+ EXT_CONTROL_REG_BITS(VMMC, 0, 0),
+};
+
+static unsigned int tps65911_ext_sleep_control[] = {
+ 0,
+ EXT_CONTROL_REG_BITS(VIO, 1, 0),
+ EXT_CONTROL_REG_BITS(VDD1, 1, 1),
+ EXT_CONTROL_REG_BITS(VDD2, 1, 2),
+ EXT_CONTROL_REG_BITS(VDDCTRL, 1, 3),
+ EXT_CONTROL_REG_BITS(LDO1, 0, 1),
+ EXT_CONTROL_REG_BITS(LDO2, 0, 2),
+ EXT_CONTROL_REG_BITS(LDO3, 0, 7),
+ EXT_CONTROL_REG_BITS(LDO4, 0, 6),
+ EXT_CONTROL_REG_BITS(LDO5, 0, 3),
+ EXT_CONTROL_REG_BITS(LDO6, 0, 0),
+ EXT_CONTROL_REG_BITS(LDO7, 0, 5),
+ EXT_CONTROL_REG_BITS(LDO8, 0, 4),
+};
+
struct tps65910_reg {
struct regulator_desc *desc;
struct tps65910 *mfd;
@@ -261,6 +297,8 @@ struct tps65910_reg {
int num_regulators;
int mode;
int (*get_ctrl_reg)(int);
+ unsigned int *ext_sleep_control;
+ unsigned int board_ext_control[TPS65910_NUM_REGS];
};
static inline int tps65910_read(struct tps65910_reg *pmic, u8 reg)
@@ -861,6 +899,131 @@ static struct regulator_ops tps65911_ops = {
.list_voltage = tps65911_list_voltage,
};
+static int tps65910_set_ext_sleep_config(struct tps65910_reg *pmic,
+ int id, int ext_sleep_config)
+{
+ struct tps65910 *mfd = pmic->mfd;
+ u8 regoffs = (pmic->ext_sleep_control[id] >> 8) & 0xFF;
+ u8 bit_pos = (1 << pmic->ext_sleep_control[id] & 0xFF);
+ int ret;
+
+ /*
+ * Regulator can not be control from multiple external input EN1, EN2
+ * and EN3 together.
+ */
+ if (ext_sleep_config & EXT_SLEEP_CONTROL) {
+ int en_count;
+ en_count = ((ext_sleep_config &
+ TPS65910_SLEEP_CONTROL_EXT_INPUT_EN1) != 0);
+ en_count += ((ext_sleep_config &
+ TPS65910_SLEEP_CONTROL_EXT_INPUT_EN2) != 0);
+ en_count += ((ext_sleep_config &
+ TPS65910_SLEEP_CONTROL_EXT_INPUT_EN3) != 0);
+ if (en_count > 1) {
+ dev_err(mfd->dev,
+ "External sleep control flag is not proper\n");
+ return -EINVAL;
+ }
+ }
+
+ pmic->board_ext_control[id] = ext_sleep_config;
+
+ /* External EN1 control */
+ if (ext_sleep_config & TPS65910_SLEEP_CONTROL_EXT_INPUT_EN1)
+ ret = tps65910_set_bits(mfd,
+ TPS65910_EN1_LDO_ASS + regoffs, bit_pos);
+ else
+ ret = tps65910_clear_bits(mfd,
+ TPS65910_EN1_LDO_ASS + regoffs, bit_pos);
+ if (ret < 0) {
+ dev_err(mfd->dev,
+ "Error in configuring external control EN1\n");
+ return ret;
+ }
+
+ /* External EN2 control */
+ if (ext_sleep_config & TPS65910_SLEEP_CONTROL_EXT_INPUT_EN2)
+ ret = tps65910_set_bits(mfd,
+ TPS65910_EN2_LDO_ASS + regoffs, bit_pos);
+ else
+ ret = tps65910_clear_bits(mfd,
+ TPS65910_EN2_LDO_ASS + regoffs, bit_pos);
+ if (ret < 0) {
+ dev_err(mfd->dev,
+ "Error in configuring external control EN2\n");
+ return ret;
+ }
+
+ /* External EN3 control for TPS65910 LDO only */
+ if ((tps65910_chip_id(mfd) == TPS65910) &&
+ (id >= TPS65910_REG_VDIG1)) {
+ if (ext_sleep_config & TPS65910_SLEEP_CONTROL_EXT_INPUT_EN3)
+ ret = tps65910_set_bits(mfd,
+ TPS65910_EN3_LDO_ASS + regoffs, bit_pos);
+ else
+ ret = tps65910_clear_bits(mfd,
+ TPS65910_EN3_LDO_ASS + regoffs, bit_pos);
+ if (ret < 0) {
+ dev_err(mfd->dev,
+ "Error in configuring external control EN3\n");
+ return ret;
+ }
+ }
+
+ /* Return if no external control is selected */
+ if (!(ext_sleep_config & EXT_SLEEP_CONTROL)) {
+ /* Clear all sleep controls */
+ ret = tps65910_clear_bits(mfd,
+ TPS65910_SLEEP_KEEP_LDO_ON + regoffs, bit_pos);
+ if (!ret)
+ ret = tps65910_clear_bits(mfd,
+ TPS65910_SLEEP_SET_LDO_OFF + regoffs, bit_pos);
+ if (ret < 0)
+ dev_err(mfd->dev,
+ "Error in configuring SLEEP register\n");
+ return ret;
+ }
+
+ /*
+ * For regulator that has separate operational and sleep register make
+ * sure that operational is used and clear sleep register to turn
+ * regulator off when external control is inactive
+ */
+ if ((id == TPS65910_REG_VDD1) ||
+ (id == TPS65910_REG_VDD2) ||
+ ((id == TPS65911_REG_VDDCTRL) &&
+ (tps65910_chip_id(mfd) == TPS65911))) {
+ int op_reg_add = pmic->get_ctrl_reg(id) + 1;
+ int sr_reg_add = pmic->get_ctrl_reg(id) + 2;
+ int opvsel = tps65910_reg_read(pmic, op_reg_add);
+ int srvsel = tps65910_reg_read(pmic, sr_reg_add);
+ if (opvsel & VDD1_OP_CMD_MASK) {
+ u8 reg_val = srvsel & VDD1_OP_SEL_MASK;
+ ret = tps65910_reg_write(pmic, op_reg_add, reg_val);
+ if (ret < 0) {
+ dev_err(mfd->dev,
+ "Error in configuring op register\n");
+ return ret;
+ }
+ }
+ ret = tps65910_reg_write(pmic, sr_reg_add, 0);
+ if (ret < 0) {
+ dev_err(mfd->dev, "Error in settting sr register\n");
+ return ret;
+ }
+ }
+
+ ret = tps65910_clear_bits(mfd,
+ TPS65910_SLEEP_KEEP_LDO_ON + regoffs, bit_pos);
+ if (!ret)
+ ret = tps65910_set_bits(mfd,
+ TPS65910_SLEEP_SET_LDO_OFF + regoffs, bit_pos);
+ if (ret < 0)
+ dev_err(mfd->dev,
+ "Error in configuring SLEEP register\n");
+ return ret;
+}
+
static __devinit int tps65910_probe(struct platform_device *pdev)
{
struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent);
@@ -891,11 +1054,13 @@ static __devinit int tps65910_probe(struct platform_device *pdev)
case TPS65910:
pmic->get_ctrl_reg = &tps65910_get_ctrl_register;
pmic->num_regulators = ARRAY_SIZE(tps65910_regs);
+ pmic->ext_sleep_control = tps65910_ext_sleep_control;
info = tps65910_regs;
break;
case TPS65911:
pmic->get_ctrl_reg = &tps65911_get_ctrl_register;
pmic->num_regulators = ARRAY_SIZE(tps65911_regs);
+ pmic->ext_sleep_control = tps65911_ext_sleep_control;
info = tps65911_regs;
break;
default:
@@ -958,6 +1123,16 @@ static __devinit int tps65910_probe(struct platform_device *pdev)
pmic->desc[i].ops = &tps65911_ops;
}
+ err = tps65910_set_ext_sleep_config(pmic, i,
+ pmic_plat_data->regulator_ext_sleep_control[i]);
+ /*
+ * Failing on regulator for configuring externally control
+ * is not a serious issue, just throw warning.
+ */
+ if (err < 0)
+ dev_warn(tps65910->dev,
+ "Failed to initialise ext control config\n");
+
pmic->desc[i].type = REGULATOR_VOLTAGE;
pmic->desc[i].owner = THIS_MODULE;
@@ -1004,6 +1179,36 @@ static int __devexit tps65910_remove(struct platform_device *pdev)
return 0;
}
+static void tps65910_shutdown(struct platform_device *pdev)
+{
+ struct tps65910_reg *pmic = platform_get_drvdata(pdev);
+ int i;
+
+ /*
+ * Before bootloader jumps to kernel, it makes sure that required
+ * external control signals are in desired state so that given rails
+ * can be configure accordingly.
+ * If rails are configured to be controlled from external control
+ * then before shutting down/rebooting the system, the external
+ * control configuration need to be remove from the rails so that
+ * its output will be available as per register programming even
+ * if external controls are removed. This is require when the POR
+ * value of the control signals are not in active state and before
+ * bootloader initializes it, the system requires the rail output
+ * to be active for booting.
+ */
+ for (i = 0; i < pmic->num_regulators; i++) {
+ int err;
+ if (!pmic->rdev[i])
+ continue;
+
+ err = tps65910_set_ext_sleep_config(pmic, i, 0);
+ if (err < 0)
+ dev_err(&pdev->dev,
+ "Error in clearing external control\n");
+ }
+}
+
static struct platform_driver tps65910_driver = {
.driver = {
.name = "tps65910-pmic",
@@ -1011,6 +1216,7 @@ static struct platform_driver tps65910_driver = {
},
.probe = tps65910_probe,
.remove = __devexit_p(tps65910_remove),
+ .shutdown = tps65910_shutdown,
};
static int __init tps65910_init(void)
diff --git a/include/linux/mfd/tps65910.h b/include/linux/mfd/tps65910.h
index d0cb12eba402..fa6c6bf7a54d 100644
--- a/include/linux/mfd/tps65910.h
+++ b/include/linux/mfd/tps65910.h
@@ -768,6 +768,13 @@
/* Max number of TPS65910/11 regulators */
#define TPS65910_NUM_REGS 13
+/* External sleep controls through EN1/EN2/EN3 inputs*/
+#define TPS65910_SLEEP_CONTROL_EXT_INPUT_EN1 0x1
+#define TPS65910_SLEEP_CONTROL_EXT_INPUT_EN2 0x2
+#define TPS65910_SLEEP_CONTROL_EXT_INPUT_EN3 0x4
+/* TPS65911 names the EN3 signal as SLEEP */
+#define TPS65911_SLEEP_CONTROL_EXT_INPUT_SLEEP 0x4
+
/**
* struct tps65910_board
* Board platform data may be used to initialize regulators.
@@ -779,6 +786,7 @@ struct tps65910_board {
int irq_base;
int vmbch_threshold;
int vmbch2_threshold;
+ unsigned long regulator_ext_sleep_control[TPS65910_NUM_REGS];
struct regulator_init_data *tps65910_pmic_init_data[TPS65910_NUM_REGS];
};