2011-04-14 16:00:23

by Jorge Eduardo Candelaria

[permalink] [raw]
Subject: [PATCH 4/4] TPS65910: Add tps65910 regulator driver

From: Graeme Gregory <[email protected]>

The regulator module consists of 3 DCDCs and 8 LDOs. The output
voltages are configurable and are meant to supply power to the
main processor and other components

Signed-off-by: Graeme Gregory <[email protected]>
Signed-off-by: Jorge Eduardo Candelaria <[email protected]>
---
drivers/regulator/Kconfig | 6 +
drivers/regulator/Makefile | 1 +
drivers/regulator/tps65910-regulator.c | 817 ++++++++++++++++++++++++++++++++
include/linux/mfd/tps65910.h | 7 +
4 files changed, 831 insertions(+), 0 deletions(-)
create mode 100644 drivers/regulator/tps65910-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index b9f29e0..fa61fe2 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -290,5 +290,11 @@ config REGULATOR_TPS6524X
serial interface currently supported on the sequencer serial
port controller.

+config REGULATOR_TPS65910
+ tristate "TI TPS65910 Power Regulator"
+ depends on MFD_TPS65910
+ help
+ This driver supports TPS65910 voltage regulator chips.
+
endif

diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index d72a427..8cb6d0d 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -41,5 +41,6 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o
obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o
obj-$(CONFIG_REGULATOR_ISL6271A) += isl6271a-regulator.o
obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o
+obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o

ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/tps65910-regulator.c b/drivers/regulator/tps65910-regulator.c
new file mode 100644
index 0000000..2738461
--- /dev/null
+++ b/drivers/regulator/tps65910-regulator.c
@@ -0,0 +1,817 @@
+/*
+ * tps65910.c -- TI tps65910
+ *
+ * Copyright 2010 Texas Instruments Inc.
+ *
+ * Author: Graeme Gregory <[email protected]>
+ * Author: Jorge Eduardo Candelaria <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/mfd/tps65910.h>
+
+#define TPS65910_REG_VRTC 0
+#define TPS65910_REG_VIO 1
+#define TPS65910_REG_VDD1 2
+#define TPS65910_REG_VDD2 3
+#define TPS65910_REG_VDD3 4
+#define TPS65910_REG_VDIG1 5
+#define TPS65910_REG_VDIG2 6
+#define TPS65910_REG_VPLL 7
+#define TPS65910_REG_VDAC 8
+#define TPS65910_REG_VAUX1 9
+#define TPS65910_REG_VAUX2 10
+#define TPS65910_REG_VAUX33 11
+#define TPS65910_REG_VMMC 12
+
+#define TPS65910_NUM_REGULATOR 13
+
+#define TPS65910_SUPPLY_STATE_ENABLED 0x1
+
+/* supported VIO voltages in milivolts */
+static const u16 VIO_VSEL_table[] = {
+ 1500, 1800, 2500, 3300,
+};
+
+/* supported VDD 1 & 2 voltages (in 0.1 milliVolts) at 1x */
+static const u16 VDD1_2_VSEL_table[] = {
+ 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875,
+ 7000, 7125, 7250, 7375, 7500, 7625, 7750, 7875,
+ 8000, 8125, 8250, 8375, 8500, 8625, 8750, 8875,
+ 9000, 9125, 9250, 9375, 9500, 9625, 9750, 9875,
+ 10000, 10125, 10250, 10375, 10500, 10625, 10750, 10875,
+ 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875,
+ 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875,
+ 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875,
+ 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875,
+ 15000,
+};
+
+#define DCDC_TABLE_SIZE ARRAY_SIZE(VDD1_2_VSEL_table)
+
+/* supported VIO voltages in milivolts */
+static const u16 VDD3_VSEL_table[] = {
+ 5000,
+};
+
+/* supported VDIG1 voltages in milivolts */
+static const u16 VDIG1_VSEL_table[] = {
+ 1200, 1500, 1800, 2700,
+};
+
+/* supported VDIG2 voltages in milivolts */
+static const u16 VDIG2_VSEL_table[] = {
+ 1000, 1100, 1200, 1800,
+};
+
+/* supported VPLL voltages in milivolts */
+static const u16 VPLL_VSEL_table[] = {
+ 1000, 1100, 1800, 2500,
+};
+
+/* supported VDAC voltages in milivolts */
+static const u16 VDAC_VSEL_table[] = {
+ 1800, 2600, 2800, 2850,
+};
+
+/* supported VAUX1 voltages in milivolts */
+static const u16 VAUX1_VSEL_table[] = {
+ 1800, 2500, 2800, 2850,
+};
+
+/* supported VAUX2 voltages in milivolts */
+static const u16 VAUX2_VSEL_table[] = {
+ 1800, 2800, 2900, 3300,
+};
+
+/* supported VAUX33 voltages in milivolts */
+static const u16 VAUX33_VSEL_table[] = {
+ 1800, 2000, 2800, 3300,
+};
+
+/* supported VMMC voltages in milivolts */
+static const u16 VMMC_VSEL_table[] = {
+ 1800, 2800, 3000, 3300,
+};
+
+struct tps_info {
+ const char *name;
+ unsigned min_uV;
+ unsigned max_uV;
+ u8 table_len;
+ const u16 *table;
+};
+
+static struct tps_info tps65910_regs[] = {
+ {
+ .name = "VRTC",
+ },
+ {
+ .name = "VIO",
+ .min_uV = 1500000,
+ .max_uV = 3300000,
+ .table_len = ARRAY_SIZE(VIO_VSEL_table),
+ .table = VIO_VSEL_table,
+ },
+ {
+ .name = "VDD1",
+ .min_uV = 600000,
+ .max_uV = 4500000,
+ .table_len = ARRAY_SIZE(VDD1_2_VSEL_table) * 3,
+ .table = VDD1_2_VSEL_table,
+ },
+ {
+ .name = "VDD2",
+ .min_uV = 600000,
+ .max_uV = 4500000,
+ .table_len = ARRAY_SIZE(VDD1_2_VSEL_table) * 3,
+ .table = VDD1_2_VSEL_table,
+ },
+ {
+ .name = "VDD3",
+ .min_uV = 5000000,
+ .max_uV = 5000000,
+ .table_len = ARRAY_SIZE(VDD3_VSEL_table),
+ .table = VDD3_VSEL_table,
+ },
+ {
+ .name = "VDIG1",
+ .min_uV = 1200000,
+ .max_uV = 2700000,
+ .table_len = ARRAY_SIZE(VDIG1_VSEL_table),
+ .table = VDIG1_VSEL_table,
+ },
+ {
+ .name = "VDIG2",
+ .min_uV = 1000000,
+ .max_uV = 1800000,
+ .table_len = ARRAY_SIZE(VDIG2_VSEL_table),
+ .table = VDIG2_VSEL_table,
+ },
+ {
+ .name = "VPLL",
+ .min_uV = 1000000,
+ .max_uV = 2500000,
+ .table_len = ARRAY_SIZE(VPLL_VSEL_table),
+ .table = VPLL_VSEL_table,
+ },
+ {
+ .name = "VDAC",
+ .min_uV = 1800000,
+ .max_uV = 2850000,
+ .table_len = ARRAY_SIZE(VDAC_VSEL_table),
+ .table = VDAC_VSEL_table,
+ },
+ {
+ .name = "VAUX1",
+ .min_uV = 1800000,
+ .max_uV = 2850000,
+ .table_len = ARRAY_SIZE(VAUX1_VSEL_table),
+ .table = VAUX1_VSEL_table,
+ },
+ {
+ .name = "VAUX2",
+ .min_uV = 1800000,
+ .max_uV = 3300000,
+ .table_len = ARRAY_SIZE(VAUX2_VSEL_table),
+ .table = VAUX2_VSEL_table,
+ },
+ {
+ .name = "VAUX33",
+ .min_uV = 1800000,
+ .max_uV = 3300000,
+ .table_len = ARRAY_SIZE(VAUX33_VSEL_table),
+ .table = VAUX33_VSEL_table,
+ },
+ {
+ .name = "VMMC",
+ .min_uV = 1800000,
+ .max_uV = 3300000,
+ .table_len = ARRAY_SIZE(VMMC_VSEL_table),
+ .table = VMMC_VSEL_table,
+ },
+};
+
+struct tps65910_reg {
+ struct regulator_desc desc[tps65910_NUM_REGULATOR];
+ struct tps65910 *mfd;
+ struct regulator_dev *rdev[tps65910_NUM_REGULATOR];
+ struct tps_info *info[tps65910_NUM_REGULATOR];
+ struct mutex mutex;
+ int mode;
+};
+
+static inline int tps65910_read(struct tps65910_reg *pmic, u8 reg)
+{
+ u8 val;
+ int err;
+
+ err = pmic->mfd->read(pmic->mfd, reg, 1, &val);
+ if (err)
+ return err;
+
+ return val;
+}
+
+static inline int tps65910_write(struct tps65910_reg *pmic, u8 reg, u8 val)
+{
+ return pmic->mfd->write(pmic->mfd, reg, 1, &val);
+}
+
+static int tps65910_set_bits(struct tps65910_reg *pmic, u8 reg, u8 mask)
+{
+ int err, data;
+
+ mutex_lock(&pmic->mutex);
+
+ data = tps65910_read(pmic, reg);
+ if (data < 0) {
+ dev_err(pmic->mfd->dev, "Read from reg 0x%x failed\n", reg);
+ err = data;
+ goto out;
+ }
+
+ data |= mask;
+ err = tps65910_write(pmic, reg, data);
+ if (err)
+ dev_err(pmic->mfd->dev, "Write for reg 0x%x failed\n", reg);
+
+out:
+ mutex_unlock(&pmic->mutex);
+ return err;
+}
+
+static int tps65910_clear_bits(struct tps65910_reg *pmic, u8 reg, u8 mask)
+{
+ int err, data;
+
+ mutex_lock(&pmic->mutex);
+
+ data = tps65910_read(pmic, reg);
+ if (data < 0) {
+ dev_err(pmic->mfd->dev, "Read from reg 0x%x failed\n", reg);
+ err = data;
+ goto out;
+ }
+
+ data &= ~mask;
+ err = tps65910_write(pmic, reg, data);
+ if (err)
+ dev_err(pmic->mfd->dev, "Write for reg 0x%x failed\n", reg);
+
+out:
+ mutex_unlock(&pmic->mutex);
+ return err;
+}
+
+static int tps65910_reg_read(struct tps65910_reg *pmic, u8 reg)
+{
+ int data;
+
+ mutex_lock(&pmic->mutex);
+
+ data = tps65910_read(pmic, reg);
+ if (data < 0)
+ dev_err(pmic->mfd->dev, "Read from reg 0x%x failed\n", reg);
+
+ mutex_unlock(&pmic->mutex);
+ return data;
+}
+
+static int tps65910_reg_write(struct tps65910_reg *pmic, u8 reg, u8 val)
+{
+ int err;
+
+ mutex_lock(&pmic->mutex);
+
+ err = tps65910_write(pmic, reg, val);
+ if (err < 0)
+ dev_err(pmic->mfd->dev, "Write for reg 0x%x failed\n", reg);
+
+ mutex_unlock(&pmic->mutex);
+ return err;
+}
+
+static int tps65910_get_ctrl_register(int id)
+{
+ switch (id) {
+ case TPS65910_VRTC_:
+ return TPS65910_VRTC;
+ case TPS65910_REG_VIO:
+ return TPS65910_VIO;
+ case TPS65910_REG_VDD1:
+ return TPS65910_VDD1;
+ case TPS65910_REG_VDD2:
+ return TPS65910_VDD2;
+ case TPS65910_REG_VDD3:
+ return TPS65910_VDD3;
+ case TPS65910_REG_VDIG1:
+ return TPS65910_VDIG1;
+ case TPS65910_REG_VDIG2:
+ return TPS65910_VDIG2;
+ case TPS65910_REG_VPLL:
+ return TPS65910_VPLL;
+ case TPS65910_REG_VDAC:
+ return TPS65910_VDAC;
+ case TPS65910_REG_VAUX1:
+ return TPS65910_VAUX1;
+ case TPS65910_REG_VAUX2:
+ return TPS65910_VAUX2;
+ case TPS65910_REG_VAUX33:
+ return TPS65910_VAUX33;
+ case TPS65910_REG_VMMC:
+ return TPS65910_VMMC;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int tps65910_is_enabled(struct regulator_dev *dev)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev);
+
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ return value & TPS65910_SUPPLY_STATE_ENABLED;
+}
+
+static int tps65910_enable(struct regulator_dev *dev)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev);
+
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ value |= TPS65910_SUPPLY_STATE_ENABLED;
+ return tps65910_reg_write(pmic, reg, value);
+}
+
+static int tps65910_disable(struct regulator_dev *dev)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev);
+
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ value &= ~TPS65910_SUPPLY_STATE_ENABLED;
+ return tps65910_reg_write(pmic, reg, value);
+}
+
+
+static int tps65910_set_mode(struct regulator_dev *dev, unsigned int mode)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev);
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ value |= LDO_ST_ON_BIT & ~LDO_ST_MODE_BIT;
+ break;
+ case REGULATOR_MODE_IDLE:
+ value |= LDO_ST_ON_BIT | LDO_ST_MODE_BIT;
+ break;
+ case REGULATOR_MODE_STANDBY:
+ value &= ~LDO_ST_ON_BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return tps65910_reg_write(pmic, reg, value);
+}
+
+static unsigned int tps65910_get_mode(struct regulator_dev *dev)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev);
+
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ if (value & LDO_ST_ON_BIT)
+ return REGULATOR_MODE_STANDBY;
+ else if (value & LDO_ST_MODE_BIT)
+ return REGULATOR_MODE_IDLE;
+ else
+ return REGULATOR_MODE_NORMAL;
+}
+
+static int tps65910_get_voltage_dcdc(struct regulator_dev *dev)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int id = rdev_get_id(dev), voltage = 0;
+ int opvsel = 0, srvsel = 0, mult = 0, sr = 0;
+
+ switch (id) {
+ case TPS65910_REG_VDD1:
+ opvsel = tps65910_reg_read(pmic, TPS65910_VDD1_OP);
+ mult = tps65910_reg_read(pmic, TPS65910_VDD1);
+ mult = (mult & VDD1_VGAIN_SEL_MASK) >> VDD1_VGAIN_SEL_SHIFT;
+ srvsel = tps65910_reg_read(pmic, TPS65910_VDD1_SR);
+ sr = opvsel & VDD1_OP_CMD_MASK;
+ opvsel &= VDD1_OP_SEL_MASK;
+ srvsel &= VDD1_SR_SEL_MASK;
+ break;
+ case TPS65910_REG_VDD2:
+ opvsel = tps65910_reg_read(pmic, TPS65910_VDD2_OP);
+ mult = tps65910_reg_read(pmic, TPS65910_VDD2);
+ mult = (mult & VDD2_VGAIN_SEL_MASK) >> VDD2_VGAIN_SEL_SHIFT;
+ srvsel = tps65910_reg_read(pmic, TPS65910_VDD2_SR);
+ sr = opvsel & VDD2_OP_CMD_MASK;
+ opvsel &= VDD2_OP_SEL_MASK;
+ srvsel &= VDD2_SR_SEL_MASK;
+ break;
+ }
+
+ /* multiplier 0 == 1 but 2,3 normal */
+ if (!mult)
+ mult=1;
+
+ if (sr) {
+ /* Valid range is 3-75 so normalise */
+ if (srvsel < 3) srvsel = 3;
+ if (srvsel > 75) srvsel = 75;
+ srvsel -= 3;
+
+ voltage = pmic->info[id]->table[srvsel] * 100;
+ } else {
+
+ /* Valid range is 3-75 so normalise */
+ if (opvsel < 3) opvsel = 3;
+ if (opvsel > 75) opvsel = 75;
+ opvsel -= 3;
+
+ voltage = pmic->info[id]->table[opvsel] * 100;
+ }
+
+ voltage *= mult;
+
+ return voltage;
+}
+
+static int tps65910_get_voltage(struct regulator_dev *dev)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev), voltage = 0;
+
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ switch (id) {
+ case TPS65910_REG_VDD3:
+ return 5 * 1000 * 1000;
+ case TPS65910_REG_VIO:
+ case TPS65910_REG_VDIG1:
+ case TPS65910_REG_VDIG2:
+ case TPS65910_REG_VPLL:
+ case TPS65910_REG_VDAC:
+ case TPS65910_REG_VAUX1:
+ case TPS65910_REG_VAUX2:
+ case TPS65910_REG_VAUX33:
+ case TPS65910_REG_VMMC:
+ value &= LDO_SEL_MASK;
+ value >>= LDO_SEL_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ voltage = pmic->info[id]->table[value] * 1000;
+
+ return voltage;
+}
+
+static int tps65910_set_voltage_dcdc_sel(struct regulator_dev *dev,
+ int selector)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, id = rdev_get_id(dev), vsel;
+ int dcdc_mult;
+
+ /* Split vsel into appropriate registers */
+ dcdc_mult = (selector / DCDC_TABLE_SIZE) + 1;
+ if (dcdc_mult == 1) dcdc_mult--;
+
+ vsel = (selector % DCDC_TABLE_SIZE) + 3;
+
+ if (id == TPS65910_REG_VDD1) {
+ reg = tps65910_reg_read(pmic, TPS65910_VDD1);
+ reg &= ~VDD1_VGAIN_SEL_MASK;
+ reg |= (dcdc_mult << VDD1_VGAIN_SEL_SHIFT);
+ tps65910_reg_write(pmic, TPS65910_VDD1, reg);
+ tps65910_reg_write(pmic, TPS65910_VDD1_OP, vsel);
+ } else {
+ reg = tps65910_reg_read(pmic, TPS65910_VDD2);
+ reg &= ~VDD2_VGAIN_SEL_MASK;
+ reg |= dcdc_mult << VDD2_VGAIN_SEL_SHIFT;
+ tps65910_reg_write(pmic, TPS65910_VDD2, reg);
+ tps65910_reg_write(pmic, TPS65910_VDD2_OP, vsel);
+ }
+
+ return 0;
+}
+
+static int tps65910_set_voltage_dcdc(struct regulator_dev *dev,
+ int min_uV, int max_uV, unsigned * selector)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int id = rdev_get_id(dev), vsel;
+
+ /* check valid voltage */
+ if (min_uV < pmic->info[id]->min_uV
+ || min_uV > pmic->info[id]->max_uV)
+ return -EINVAL;
+ if (max_uV < pmic->info[id]->min_uV
+ || max_uV > pmic->info[id]->max_uV)
+ return -EINVAL;
+
+ /* find requested voltage in table */
+ for (vsel = 0; vsel < pmic->info[id]->table_len; vsel++) {
+ int mult = (vsel / DCDC_TABLE_SIZE) + 1;
+ int mV = pmic->info[id]->table[vsel % DCDC_TABLE_SIZE] * mult;
+ int uV = mV * 100;
+
+ /* Break at the first in-range value */
+ if (min_uV <= uV && uV <= max_uV)
+ break;
+ }
+
+ if (vsel == pmic->info[id]->table_len)
+ return -EINVAL;
+
+ return tps65910_set_voltage_dcdc_sel(dev, vsel);
+}
+
+static int tps65910_set_voltage(struct regulator_dev *dev,
+ int min_uV, int max_uV, unsigned *selector)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int reg, value, id = rdev_get_id(dev), vsel;
+
+ /* check valid voltage */
+ if (min_uV < pmic->info[id]->min_uV
+ || min_uV > pmic->info[id]->max_uV)
+ return -EINVAL;
+ if (max_uV < pmic->info[id]->min_uV
+ || max_uV > pmic->info[id]->max_uV)
+ return -EINVAL;
+
+ /* find requested voltage in table */
+ for (vsel = 0; vsel < pmic->info[id]->table_len; vsel++) {
+ int mV = pmic->info[id]->table[vsel];
+ int uV;
+
+ uV = mV * 1000;
+
+ /* Break at the first in-range value */
+ if (min_uV <= uV && uV <= max_uV)
+ break;
+ }
+
+ reg = tps65910_get_ctrl_register(id);
+ if (reg < 0)
+ return reg;
+
+ value = tps65910_reg_read(pmic, reg);
+ if (value < 0)
+ return value;
+
+ switch (id) {
+ case TPS65910_REG_VDD3:
+ return 0;
+ case TPS65910_REG_VIO:
+ case TPS65910_REG_VDIG1:
+ case TPS65910_REG_VDIG2:
+ case TPS65910_REG_VPLL:
+ case TPS65910_REG_VDAC:
+ case TPS65910_REG_VAUX1:
+ case TPS65910_REG_VAUX2:
+ case TPS65910_REG_VAUX33:
+ case TPS65910_REG_VMMC:
+ value &= ~(LDO_SEL_MASK);
+ value |= (vsel << LDO_SEL_SHIFT);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return tps65910_reg_write(pmic, reg, value);
+}
+
+static int tps65910_list_voltage_dcdc(struct regulator_dev *dev,
+ unsigned selector)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int id = rdev_get_id(dev);
+ int mult;
+
+ mult = (selector / DCDC_TABLE_SIZE) + 1;
+
+ return pmic->info[id]->table[selector % DCDC_TABLE_SIZE] * 100 * mult;
+}
+
+static int tps65910_list_voltage(struct regulator_dev *dev,
+ unsigned selector)
+{
+ struct tps65910_reg *pmic = rdev_get_drvdata(dev);
+ int id = rdev_get_id(dev), voltage;
+
+ if (id < TPS65910_REG_VIO || id > TPS65910_REG_VMMC)
+ return -EINVAL;
+
+ if (selector >= pmic->info[id]->table_len)
+ return -EINVAL;
+ else
+ voltage = pmic->info[id]->table[selector] * 1000;
+
+ return voltage;
+}
+
+/* Regulator ops (except VRTC) */
+static struct regulator_ops tps65910_ops_dcdc = {
+ .is_enabled = tps65910_is_enabled,
+ .enable = tps65910_enable,
+ .disable = tps65910_disable,
+ .set_mode = tps65910_set_mode,
+ .get_mode = tps65910_get_mode,
+ .get_voltage = tps65910_get_voltage_dcdc,
+ .set_voltage = tps65910_set_voltage_dcdc,
+ .list_voltage = tps65910_list_voltage_dcdc,
+};
+
+static struct regulator_ops tps65910_ops = {
+ .is_enabled = tps65910_is_enabled,
+ .enable = tps65910_enable,
+ .disable = tps65910_disable,
+ .set_mode = tps65910_set_mode,
+ .get_mode = tps65910_get_mode,
+ .get_voltage = tps65910_get_voltage,
+ .set_voltage = tps65910_set_voltage,
+ .list_voltage = tps65910_list_voltage,
+};
+
+/* VRTC Regulator ops */
+static struct regulator_ops tps65910_vrtc_ops = {
+ .is_enabled = tps65910_is_enabled,
+};
+
+static __devinit int tps65910_probe(struct platform_device *pdev)
+{
+ struct tps65910 *tps65910 = dev_get_drvdata(pdev->dev.parent);
+ struct tps_info *info = tps65910_regs;
+ struct regulator_init_data *reg_data;
+ struct regulator_dev *rdev;
+ struct tps65910_reg *pmic;
+ struct tps65910_board *pmic_plat_data;
+ static int desc_id;
+ int i, err;
+ u8 reg;
+
+ pmic_plat_data = dev_get_platdata(tps65910->dev);
+ if (!pmic_plat_data)
+ return -EINVAL;
+
+ reg_data = pmic_plat_data->tps65910_pmic_init_data;
+ if (!reg_data)
+ return -EINVAL;
+
+ pmic = kzalloc(sizeof(*pmic), GFP_KERNEL);
+ if (!pmic)
+ return -ENOMEM;
+
+ mutex_init(&pmic->mutex);
+ pmic->mfd = tps65910;
+ platform_set_drvdata(pdev, pmic);
+
+ /* Give control of all register to control port */
+ reg = tps65910_reg_read(pmic, TPS65910_DEVCTRL);
+ reg |= DEVCTRL_SR_CTL_I2C_SEL_MASK;
+ tps65910_reg_write(pmic, TPS65910_DEVCTRL, reg);
+
+ for (i = 0; i < tps65910_NUM_REGULATOR; i++, info++, reg_data++) {
+ /* Register the regulators */
+ pmic->info[i] = info;
+
+ pmic->desc[i].name = info->name;
+ pmic->desc[i].id = desc_id++;
+ pmic->desc[i].n_voltages = info->table_len;
+
+ if ((i == TPS65910_REG_VDD1) || (i == TPS65910_REG_VDD2))
+ pmic->desc[i].ops = &tps65910_ops_dcdc;
+ else
+ pmic->desc[i].ops = &tps65910_ops;
+
+ pmic->desc[i].type = REGULATOR_VOLTAGE;
+ pmic->desc[i].owner = THIS_MODULE;
+
+ rdev = regulator_register(&pmic->desc[i],
+ tps65910->dev, reg_data, pmic);
+ if (IS_ERR(rdev)) {
+ dev_err(tps65910->dev,
+ "failed to register %s regulator\n",
+ pdev->name);
+ err = PTR_ERR(rdev);
+ goto err;
+ }
+
+ /* Save regulator for cleanup */
+ pmic->rdev[i] = rdev;
+ }
+ return 0;
+
+err:
+ while (--i >= 0)
+ regulator_unregister(pmic->rdev[i]);
+
+ kfree(pmic);
+ return err;
+}
+
+static int __devexit tps65910_remove(struct platform_device *pdev)
+{
+ struct tps65910_reg *tps65910_reg = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < tps65910_NUM_REGULATOR; i++)
+ regulator_unregister(tps65910_reg->rdev[i]);
+
+ kfree(tps65910_reg);
+ return 0;
+}
+
+static struct platform_driver tps65910_driver = {
+ .driver = {
+ .name = "tps65910-pmic",
+ .owner = THIS_MODULE,
+ },
+ .probe = tps65910_probe,
+ .remove = __devexit_p(tps65910_remove),
+};
+
+static int __init tps65910_init(void)
+{
+ return platform_driver_register(&tps65910_driver);
+}
+subsys_initcall(tps65910_init);
+
+static void __exit tps65910_cleanup(void)
+{
+ platform_driver_unregister(&tps65910_driver);
+}
+module_exit(tps65910_cleanup);
+
+MODULE_AUTHOR("Graeme Gregory <[email protected]>");
+MODULE_DESCRIPTION("TPS6507x voltage regulator driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:tps65910-pmic");
diff --git a/include/linux/mfd/tps65910.h b/include/linux/mfd/tps65910.h
index afc1896..14b1693 100644
--- a/include/linux/mfd/tps65910.h
+++ b/include/linux/mfd/tps65910.h
@@ -221,6 +221,13 @@
#define VDD3_ST_MASK 0x03
#define VDD3_ST_SHIFT 0

+/*Registers VDIG (0x80) to VDAC register.RegisterDescription */
+#define LDO_SEL_MASK 0x0C
+#define LDO_SEL_SHIFT 2
+#define LDO_ST_MASK 0x03
+#define LDO_ST_SHIFT 0
+#define LDO_ST_MODE_BIT 0x02
+

/*Register VDIG1 (0x80) register.RegisterDescription */
#define VDIG1_SEL_MASK 0x0C
--
1.7.1


2011-04-16 17:32:07

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 4/4] TPS65910: Add tps65910 regulator driver

On Thu, Apr 14, 2011 at 10:49:39AM -0500, Jorge Eduardo Candelaria wrote:

> +/* supported VDD 1 & 2 voltages (in 0.1 milliVolts) at 1x */
> +static const u16 VDD1_2_VSEL_table[] = {
> + 6000, 6125, 6250, 6375, 6500, 6625, 6750, 6875,
> + 7000, 7125, 7250, 7375, 7500, 7625, 7750, 7875,
> + 8000, 8125, 8250, 8375, 8500, 8625, 8750, 8875,
> + 9000, 9125, 9250, 9375, 9500, 9625, 9750, 9875,
> + 10000, 10125, 10250, 10375, 10500, 10625, 10750, 10875,
> + 11000, 11125, 11250, 11375, 11500, 11625, 11750, 11875,
> + 12000, 12125, 12250, 12375, 12500, 12625, 12750, 12875,
> + 13000, 13125, 13250, 13375, 13500, 13625, 13750, 13875,
> + 14000, 14125, 14250, 14375, 14500, 14625, 14750, 14875,
> + 15000,

This looks like it should be possible to replace it with a calculation?
That'd be more efficient than scanning the array.

> +/* supported VDIG1 voltages in milivolts */
> +static const u16 VDIG1_VSEL_table[] = {
> + 1200, 1500, 1800, 2700,
> +};

Tables are more suitable for voltage ranges like this which are uneven.

> +static int tps65910_set_bits(struct tps65910_reg *pmic, u8 reg, u8 mask)
> +{
> + int err, data;
> +
> + mutex_lock(&pmic->mutex);
> +
> + data = tps65910_read(pmic, reg);
> + if (data < 0) {
> + dev_err(pmic->mfd->dev, "Read from reg 0x%x failed\n", reg);
> + err = data;
> + goto out;
> + }
> +
> + data |= mask;
> + err = tps65910_write(pmic, reg, data);
> + if (err)
> + dev_err(pmic->mfd->dev, "Write for reg 0x%x failed\n", reg);
> +
> +out:
> + mutex_unlock(&pmic->mutex);
> + return err;
> +}

No real problem here but it seems like all this register I/O code would
also be useful for other drivers using the chip?

> + value = tps65910_reg_read(pmic, reg);
> + if (value < 0)
> + return value;
> +
> + value |= TPS65910_SUPPLY_STATE_ENABLED;
> + return tps65910_reg_write(pmic, reg, value);

There was a set bits operation defined above - it'd make sense to use
that here, similarly for the clear in the disable path and quite a few
other places in the driver.

> +static int tps65910_set_voltage_dcdc_sel(struct regulator_dev *dev,
> + int selector)
> +{

> +static int tps65910_set_voltage_dcdc(struct regulator_dev *dev,
> + int min_uV, int max_uV, unsigned * selector)

You only need to implement one of these, the _sel version will be
preferred by the core.

> +static int tps65910_set_voltage(struct regulator_dev *dev,
> + int min_uV, int max_uV, unsigned *selector)
> +{

...

> + /* find requested voltage in table */
> + for (vsel = 0; vsel < pmic->info[id]->table_len; vsel++) {
> + int mV = pmic->info[id]->table[vsel];
> + int uV;
> +
> + uV = mV * 1000;
> +
> + /* Break at the first in-range value */
> + if (min_uV <= uV && uV <= max_uV)
> + break;
> + }

This should be implemented based on passing in the selector - the core
can do the table iteration for you.

> + value = tps65910_reg_read(pmic, reg);
> + if (value < 0)
> + return value;
> +
> + switch (id) {
> + case TPS65910_REG_VDD3:
> + return 0;

There's a few special cases for VDD3 - it feels like it should have a
custom get voltage operation and no set rather than having this special
case. There's a similar thing in list_voltage, it feels like we're not
really doing the right thing by providing operations that don't exist.

> + pmic_plat_data = dev_get_platdata(tps65910->dev);
> + if (!pmic_plat_data)
> + return -EINVAL;
> +
> + reg_data = pmic_plat_data->tps65910_pmic_init_data;
> + if (!reg_data)
> + return -EINVAL;

You shouldn't need to check for the regulator_init_data - the core can
do that for you, and could decide to present the regulator to the
application layer for readback for use in diagnostics even if there's
nothing writable about it.