diff mbox

[3/8] Regulator: Add TPS6507x regulator driver

Message ID 1249658766-10859-1-git-send-email-anuj.aggarwal@ti.com (mailing list archive)
State Changes Requested, archived
Headers show

Commit Message

Aggarwal, Anuj Aug. 7, 2009, 3:26 p.m. UTC
Adding support for TI TPS6507x regulator driver

Signed-off-by: Anuj Aggarwal <anuj.aggarwal@ti.com>
---
 drivers/regulator/tps6507x-regulator.c |  563 ++++++++++++++++++++++++++++++++
 1 files changed, 563 insertions(+), 0 deletions(-)
 create mode 100644 drivers/regulator/tps6507x-regulator.c

Comments

Mark Brown Aug. 7, 2009, 4:02 p.m. UTC | #1
On Fri, Aug 07, 2009 at 08:56:06PM +0530, Anuj Aggarwal wrote:
> Adding support for TI TPS6507x regulator driver

Exactly which chips does this driver support?  Google tells me that at
least some of the parts in the TPS6507x range have functionality other
than regulator support on them like power path management and touch
which would mean that the drivers should be using the MFD framework to
hook in to the various subsystems unless the device presents as multiple
devices on the I2C bus.

Otherwise my comments here are essentially the same as for the previous
driver - it looks good but the same relatively minor issues apply.
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Felipe Balbi Aug. 7, 2009, 8:27 p.m. UTC | #2
On Fri, Aug 07, 2009 at 05:26:06PM +0200, ext Anuj Aggarwal wrote:
> Adding support for TI TPS6507x regulator driver
> 
> Signed-off-by: Anuj Aggarwal <anuj.aggarwal@ti.com>
> ---
>  drivers/regulator/tps6507x-regulator.c |  563 ++++++++++++++++++++++++++++++++
>  1 files changed, 563 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/regulator/tps6507x-regulator.c
> 
> diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c
> new file mode 100644
> index 0000000..c5e319c
> --- /dev/null
> +++ b/drivers/regulator/tps6507x-regulator.c
> @@ -0,0 +1,563 @@
> +/*
> + * tps6507x-regulator.c
> + *
> + * Regulator driver for TPS65073 PMIC
> + *
> + * Copyright (C) 2009 Texas Instrument Incorporated - http://www.ti.com/
> + *
> + * 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 version 2.
> + *
> + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
> + * whether express or implied; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#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/mfd/tps6507x.h>
> +#include <linux/i2c.h>
> +#include <linux/delay.h>
> +
> +struct tps_info {
> +       const char *name;
> +       unsigned min_uV;
> +       unsigned max_uV;
> +       bool fixed;
> +       u8 table_len;
> +       const u16 *table;
> +};
> +
> +struct tps_pmic {
> +       struct regulator_desc desc[TPS6507X_NUM_REGULATOR];
> +       struct i2c_client *client;
> +       struct regulator_dev *rdev[TPS6507X_NUM_REGULATOR];
> +       struct tps_info *info[TPS6507X_NUM_REGULATOR];
> +};
> +
> +static inline int tps_6507x_read_reg(struct tps_pmic *tps, u8 reg, u8 *val)
> +{
> +       int status;
> +
> +       status = i2c_smbus_read_byte_data(tps->client, reg);
> +       *val = status;
> +       if (status < 0)
> +               return status;
> +       return 0;

same comments as previous patch.

> +}
> +
> +static inline int tps_6507x_write_reg(struct tps_pmic *tps, u8 reg, u8 val)
> +{
> +       return i2c_smbus_write_byte_data(tps->client, reg, val);
> +}
> +
> +static int tps_6507x_set_bits(struct tps_pmic *tps, u8 reg, u8 mask)
> +{
> +       u8 data;
> +       int err;
> +
> +       err = tps_6507x_read_reg(tps, reg, &data);
> +       if (err) {
> +               pr_err("Read from reg 0x%x failed\n", reg);
> +               return err;
> +       }
> +
> +       data |= mask;
> +
> +       return tps_6507x_write_reg(tps, reg, data);
> +}
> +
> +static int tps_6507x_clear_bits(struct tps_pmic *tps, u8 reg, u8 mask)
> +{
> +       u8 data;
> +       int err;
> +
> +       err = tps_6507x_read_reg(tps, reg, &data);
> +       if (err) {
> +               pr_err("Read from reg 0x%x failed\n", reg);
> +               return err;
> +       }
> +
> +       data &= ~mask;
> +
> +       return tps_6507x_write_reg(tps, reg, data);
> +}
> +
> +static int tps6507x_dcdc_is_enabled(struct regulator_dev *dev)
> +{
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int dcdc = rdev_get_id(dev);
> +       int ret;
> +       u8 shift;
> +       unsigned char reg_ctrl;
> +
> +       if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3)
> +               return -EINVAL;
> +
> +       shift = TPS6507X_MAX_REG_ID - dcdc;
> +       ret = tps_6507x_read_reg(tps, TPS6507X_REG_CON_CTRL1, &reg_ctrl);
> +
> +       if (ret == 0) {
> +               reg_ctrl &= (1 << shift);
> +               return reg_ctrl ? 1 : 0;
> +       } else
> +               return ret;
> +}
> +
> +static int tps6507x_ldo_is_enabled(struct regulator_dev *dev)
> +{
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int ldo = rdev_get_id(dev);
> +       int ret;
> +       u8 shift;
> +       unsigned char reg_ctrl;
> +
> +       if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
> +               return -EINVAL;
> +
> +       shift = TPS6507X_MAX_REG_ID - ldo;
> +       ret = tps_6507x_read_reg(tps, TPS6507X_REG_CON_CTRL1, &reg_ctrl);
> +
> +       if (ret == 0) {
> +               reg_ctrl &= (1 << shift);
> +               return reg_ctrl ? 1 : 0;
> +       } else
> +               return ret;
> +}
> +
> +static int tps6507x_dcdc_enable(struct regulator_dev *dev)
> +{
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int dcdc = rdev_get_id(dev);
> +       u8 shift;
> +
> +       if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3)
> +               return -EINVAL;
> +
> +       shift = TPS6507X_MAX_REG_ID - dcdc;
> +       return tps_6507x_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
> +}
> +
> +static int tps6507x_dcdc_disable(struct regulator_dev *dev)
> +{
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int dcdc = rdev_get_id(dev);
> +       u8 shift;
> +
> +       if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3)
> +               return -EINVAL;
> +
> +       shift = TPS6507X_MAX_REG_ID - dcdc;
> +       return tps_6507x_clear_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
> +}
> +
> +static int tps6507x_ldo_enable(struct regulator_dev *dev)
> +{
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int ldo = rdev_get_id(dev);
> +       u8 shift;
> +
> +       if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
> +               return -EINVAL;
> +
> +       shift = TPS6507X_MAX_REG_ID - ldo;
> +       return tps_6507x_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
> +}
> +
> +static int tps6507x_ldo_disable(struct regulator_dev *dev)
> +{
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int ldo = rdev_get_id(dev);
> +       u8 shift;
> +
> +       if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
> +               return -EINVAL;
> +
> +       shift = TPS6507X_MAX_REG_ID - ldo;
> +       return tps_6507x_clear_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
> +}
> +
> +static int tps6507x_dcdc_get_voltage(struct regulator_dev *dev)
> +{
> +       unsigned char reg_ctrl;
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int dcdc = rdev_get_id(dev);
> +       u8 reg;
> +       int ret;
> +
> +       switch (dcdc) {
> +       case TPS6507X_DCDC_1:
> +               reg = TPS6507X_REG_DEFDCDC1;
> +               break;
> +       case TPS6507X_DCDC_2:
> +               reg = TPS6507X_REG_DEFDCDC2_LOW;
> +               break;
> +       case TPS6507X_DCDC_3:
> +               reg = TPS6507X_REG_DEFDCDC3_LOW;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
> +       if (ret < 0)
> +               return ret;
> +
> +       reg_ctrl &= TPS6507X_DEFDCDCX_DCDC_MASK;
> +       return tps->info[dcdc]->table[reg_ctrl] * 1000;
> +}
> +
> +static int tps6507x_dcdc_set_voltage(struct regulator_dev *dev,
> +                               int min_uV, int max_uV)
> +{
> +       unsigned char reg_ctrl;
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int dcdc = rdev_get_id(dev);
> +       u8 reg;
> +       int vsel, ret;
> +
> +       switch (dcdc) {
> +       case TPS6507X_DCDC_1:
> +               reg = TPS6507X_REG_DEFDCDC1;
> +               break;
> +       case TPS6507X_DCDC_2:
> +               reg = TPS6507X_REG_DEFDCDC2_LOW;
> +               break;
> +       case TPS6507X_DCDC_3:
> +               reg = TPS6507X_REG_DEFDCDC3_LOW;
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       if (min_uV < tps->info[dcdc]->min_uV
> +                       || min_uV > tps->info[dcdc]->max_uV)
> +               return -EINVAL;
> +       if (max_uV < tps->info[dcdc]->min_uV
> +                       || max_uV > tps->info[dcdc]->max_uV)
> +               return -EINVAL;
> +
> +       for (vsel = 0; vsel < tps->info[dcdc]->table_len; vsel++) {
> +               int mV = tps->info[dcdc]->table[vsel];
> +               int uV = mV * 1000;
> +
> +               /* Break at the first in-range value */
> +               if (min_uV <= uV && uV <= max_uV)
> +                       break;
> +       }
> +
> +       ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
> +       if (ret < 0)
> +               return ret;
> +
> +       reg_ctrl &= ~TPS6507X_DEFDCDCX_DCDC_MASK;
> +       reg_ctrl |= vsel;
> +
> +       return tps_6507x_write_reg(tps, reg, reg_ctrl);
> +}
> +
> +static int tps6507x_ldo_get_voltage(struct regulator_dev *dev)
> +{
> +       unsigned char reg_ctrl;
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int ldo = rdev_get_id(dev);
> +       u8 reg, mask;
> +       int ret;
> +
> +       if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
> +               return -EINVAL;
> +       else {
> +               reg = (ldo == TPS6507X_LDO_1 ?
> +                       TPS6507X_REG_LDO_CTRL1 : TPS6507X_REG_DEFLDO2);
> +               mask = (ldo == TPS6507X_LDO_1 ?
> +                       TPS6507X_REG_LDO_CTRL1_LDO1_MASK :
> +                               TPS6507X_REG_DEFLDO2_LDO2_MASK);
> +       }
> +
> +       ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
> +       if (ret < 0)
> +               return ret;
> +
> +       reg_ctrl &= mask;
> +       return tps->info[ldo]->table[reg_ctrl] * 1000;
> +}
> +
> +static int tps6507x_ldo_set_voltage(struct regulator_dev *dev,
> +                               int min_uV, int max_uV)
> +{
> +       unsigned char reg_ctrl;
> +       struct tps_pmic *tps = rdev_get_drvdata(dev);
> +       int ldo = rdev_get_id(dev);
> +       u8 reg, mask;
> +       int vsel, ret;
> +
> +       if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
> +               return -EINVAL;
> +       else {
> +               reg = (ldo == TPS6507X_LDO_1 ?
> +                       TPS6507X_REG_LDO_CTRL1 : TPS6507X_REG_DEFLDO2);
> +               mask = (ldo == TPS6507X_LDO_1 ?
> +                       TPS6507X_REG_LDO_CTRL1_LDO1_MASK :
> +                               TPS6507X_REG_DEFLDO2_LDO2_MASK);
> +       }
> +
> +       if (min_uV < tps->info[ldo]->min_uV || min_uV > tps->info[ldo]->max_uV)
> +               return -EINVAL;
> +       if (max_uV < tps->info[ldo]->min_uV || max_uV > tps->info[ldo]->max_uV)
> +               return -EINVAL;
> +
> +       for (vsel = 0; vsel < tps->info[ldo]->table_len; vsel++) {
> +               int mV = tps->info[ldo]->table[vsel];
> +               int uV = mV * 1000;
> +
> +               /* Break at the first in-range value */
> +               if (min_uV <= uV && uV <= max_uV)
> +                       break;
> +       }
> +
> +       ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
> +       if (ret < 0)
> +               return ret;
> +
> +       reg_ctrl &= ~mask;
> +       reg_ctrl |= vsel;
> +
> +       return tps_6507x_write_reg(tps, reg, reg_ctrl);
> +}
> +
> +/* Operations permitted on VDCDCx */
> +static struct regulator_ops tps6507x_dcdc_ops = {
> +       .is_enabled = tps6507x_dcdc_is_enabled,
> +       .enable = tps6507x_dcdc_enable,
> +       .disable = tps6507x_dcdc_disable,
> +       .get_voltage = tps6507x_dcdc_get_voltage,
> +       .set_voltage = tps6507x_dcdc_set_voltage,
> +};
> +
> +/* Operations permitted on LDOx */
> +static struct regulator_ops tps6507x_ldo_ops = {
> +       .is_enabled = tps6507x_ldo_is_enabled,
> +       .enable = tps6507x_ldo_enable,
> +       .disable = tps6507x_ldo_disable,
> +       .get_voltage = tps6507x_ldo_get_voltage,
> +       .set_voltage = tps6507x_ldo_set_voltage,
> +};
> +
> +static
> +int tps_6507x_probe(struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +       static int desc_id;
> +       const struct tps_info *info = (void *)id->driver_data;
> +       struct regulator_init_data *init_data;
> +       struct regulator_dev *rdev;
> +       struct tps_pmic *tps;
> +       int i;
> +
> +       if (!i2c_check_functionality(client->adapter,
> +                               I2C_FUNC_SMBUS_BYTE_DATA))
> +               return -EIO;
> +
> +       /**
> +        * init_data points to array of regulator_init structures
> +        * coming from the board-evm file.
> +        */
> +       init_data = client->dev.platform_data;
> +
> +       if (!init_data)
> +               return -EIO;
> +
> +       tps = kzalloc(sizeof(*tps), GFP_KERNEL);
> +       if (!tps)
> +               return -ENOMEM;
> +
> +       /* common for all regulators */
> +       tps->client = client;
> +
> +       for (i = 0; i < TPS6507X_NUM_REGULATOR; i++, info++, init_data++) {
> +               /* Register the regulators */
> +               tps->info[i] = info;
> +               tps->desc[i].name = info->name;
> +               tps->desc[i].id = desc_id++;
> +               tps->desc[i].ops = (i > TPS6507X_DCDC_3 ?
> +                               &tps6507x_ldo_ops : &tps6507x_dcdc_ops);
> +               tps->desc[i].type = REGULATOR_VOLTAGE;
> +               tps->desc[i].owner = THIS_MODULE;
> +
> +               rdev = regulator_register(&tps->desc[i],
> +                                       &client->dev, init_data, tps);
> +               if (IS_ERR(rdev)) {
> +                       dev_err(&client->dev, "failed to register %s\n",
> +                                       id->name);
> +                       kfree(tps);
> +                       return PTR_ERR(rdev);
> +               }
> +
> +               /* Save regulator for cleanup */
> +               tps->rdev[i] = rdev;
> +       }
> +
> +       i2c_set_clientdata(client, tps);
> +
> +       return 0;
> +}
> +
> +/**
> + * tps_6507x_remove - TPS6507x driver i2c remove handler
> + * @client: i2c driver client device structure
> + *
> + * Unregister TPS driver as an i2c client device driver
> + */
> +static int __devexit tps_6507x_remove(struct i2c_client *client)
> +{
> +       struct tps_pmic *tps = i2c_get_clientdata(client);
> +       int i;
> +
> +       for (i = 0; i < TPS6507X_NUM_REGULATOR; i++)
> +               regulator_unregister(tps->rdev[i]);
> +
> +       tps->client = NULL;
> +
> +       /* clear the client data in i2c */
> +       i2c_set_clientdata(client, NULL);
> +       kfree(tps);
> +
> +       return 0;
> +}
> +
> +/* Supported voltage values for regulators (in milliVolts) */
> +static const u16 VDCDCx_VSEL_table[] = {
> +       725, 750, 775, 800,
> +       825, 850, 875, 900,
> +       925, 950, 975, 1000,
> +       1025, 1050, 1075, 1100,
> +       1125, 1150, 1175, 1200,
> +       1225, 1250, 1275, 1300,
> +       1325, 1350, 1375, 1400,
> +       1425, 1450, 1475, 1500,
> +       1550, 1600, 1650, 1700,
> +       1750, 1800, 1850, 1900,
> +       1950, 2000, 2050, 2100,
> +       2150, 2200, 2250, 2300,
> +       2350, 2400, 2450, 2500,
> +       2550, 2600, 2650, 2700,
> +       2750, 2800, 2850, 2900,
> +       3000, 3100, 3200, 3300,
> +};
> +
> +static const u16 LDO1_VSEL_table[] = {
> +       1000, 1100, 1200, 1250,
> +       1300, 1350, 1400, 1500,
> +       1600, 1800, 2500, 2750,
> +       2800, 3000, 3100, 3300,
> +};
> +
> +static const u16 LDO2_VSEL_table[] = {
> +       725, 750, 775, 800,
> +       825, 850, 875, 900,
> +       925, 950, 975, 1000,
> +       1025, 1050, 1075, 1100,
> +       1125, 1150, 1175, 1200,
> +       1225, 1250, 1275, 1300,
> +       1325, 1350, 1375, 1400,
> +       1425, 1450, 1475, 1500,
> +       1550, 1600, 1650, 1700,
> +       1750, 1800, 1850, 1900,
> +       1950, 2000, 2050, 2100,
> +       2150, 2200, 2250, 2300,
> +       2350, 2400, 2450, 2500,
> +       2550, 2600, 2650, 2700,
> +       2750, 2800, 2850, 2900,
> +       3000, 3100, 3200, 3300,
> +};
> +
> +static const struct tps_info tps6507x_regs[] = {
> +       {
> +       .name = "VDCDC1",
> +       .min_uV = 725000,
> +       .max_uV = 3300000,
> +       .fixed = 0,
> +       .table_len = ARRAY_SIZE(VDCDCx_VSEL_table),
> +       .table = VDCDCx_VSEL_table,
> +       },
> +       {
> +       .name = "VDCDC2",
> +       .min_uV = 725000,
> +       .max_uV = 3300000,
> +       .fixed = 0,
> +       .table_len = ARRAY_SIZE(VDCDCx_VSEL_table),
> +       .table = VDCDCx_VSEL_table,
> +       },
> +       {
> +       .name = "VDCDC3",
> +       .min_uV = 725000,
> +       .max_uV = 3300000,
> +       .fixed = 0,
> +       .table_len = ARRAY_SIZE(VDCDCx_VSEL_table),
> +       .table = VDCDCx_VSEL_table,
> +       },
> +       {
> +       .name = "LDO1",
> +       .min_uV = 1000000,
> +       .max_uV = 3300000,
> +       .fixed = 0,
> +       .table_len = ARRAY_SIZE(LDO1_VSEL_table),
> +       .table = LDO1_VSEL_table,
> +       },
> +       {
> +       .name = "LDO2",
> +       .min_uV = 725000,
> +       .max_uV = 3300000,
> +       .fixed = 0,
> +       .table_len = ARRAY_SIZE(LDO2_VSEL_table),
> +       .table = LDO2_VSEL_table,
> +       },
> +};
> +
> +static const struct i2c_device_id tps_6507x_id = {
> +       .name = "tps6507x",
> +       .driver_data = (unsigned long) &tps6507x_regs[0],
> +};
> +MODULE_DEVICE_TABLE(i2c, tps_6507x_id);
> +
> +static struct i2c_driver tps_6507x_i2c_driver = {
> +       .driver = {
> +               .name = "tps_6507x_pwr",
> +               .owner = THIS_MODULE,
> +       },
> +       .probe = tps_6507x_probe,
> +       .remove = __devexit_p(tps_6507x_remove),
> +       .id_table = &tps_6507x_id,
> +};
> +
> +/**
> + * tps_6507x_init
> + *
> + * Module init function
> + */
> +static int __init tps_6507x_init(void)
> +{
> +       return i2c_add_driver(&tps_6507x_i2c_driver);
> +}
> +subsys_initcall(tps_6507x_init);
> +
> +/**
> + * tps_6507x_cleanup
> + *
> + * Module exit function
> + */
> +static void __exit tps_6507x_cleanup(void)
> +{
> +       i2c_del_driver(&tps_6507x_i2c_driver);
> +}
> +module_exit(tps_6507x_cleanup);
> +
> +MODULE_AUTHOR("Texas Instruments");
> +MODULE_DESCRIPTION("TPS6507x voltage regulator driver");
> +MODULE_LICENSE("GPLv2");
> --
> 1.6.2.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
diff mbox

Patch

diff --git a/drivers/regulator/tps6507x-regulator.c b/drivers/regulator/tps6507x-regulator.c
new file mode 100644
index 0000000..c5e319c
--- /dev/null
+++ b/drivers/regulator/tps6507x-regulator.c
@@ -0,0 +1,563 @@ 
+/*
+ * tps6507x-regulator.c
+ *
+ * Regulator driver for TPS65073 PMIC
+ *
+ * Copyright (C) 2009 Texas Instrument Incorporated - http://www.ti.com/
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
+ * whether express or implied; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#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/mfd/tps6507x.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+struct tps_info {
+	const char *name;
+	unsigned min_uV;
+	unsigned max_uV;
+	bool fixed;
+	u8 table_len;
+	const u16 *table;
+};
+
+struct tps_pmic {
+	struct regulator_desc desc[TPS6507X_NUM_REGULATOR];
+	struct i2c_client *client;
+	struct regulator_dev *rdev[TPS6507X_NUM_REGULATOR];
+	struct tps_info *info[TPS6507X_NUM_REGULATOR];
+};
+
+static inline int tps_6507x_read_reg(struct tps_pmic *tps, u8 reg, u8 *val)
+{
+	int status;
+
+	status = i2c_smbus_read_byte_data(tps->client, reg);
+	*val = status;
+	if (status < 0)
+		return status;
+	return 0;
+}
+
+static inline int tps_6507x_write_reg(struct tps_pmic *tps, u8 reg, u8 val)
+{
+	return i2c_smbus_write_byte_data(tps->client, reg, val);
+}
+
+static int tps_6507x_set_bits(struct tps_pmic *tps, u8 reg, u8 mask)
+{
+	u8 data;
+	int err;
+
+	err = tps_6507x_read_reg(tps, reg, &data);
+	if (err) {
+		pr_err("Read from reg 0x%x failed\n", reg);
+		return err;
+	}
+
+	data |= mask;
+
+	return tps_6507x_write_reg(tps, reg, data);
+}
+
+static int tps_6507x_clear_bits(struct tps_pmic *tps, u8 reg, u8 mask)
+{
+	u8 data;
+	int err;
+
+	err = tps_6507x_read_reg(tps, reg, &data);
+	if (err) {
+		pr_err("Read from reg 0x%x failed\n", reg);
+		return err;
+	}
+
+	data &= ~mask;
+
+	return tps_6507x_write_reg(tps, reg, data);
+}
+
+static int tps6507x_dcdc_is_enabled(struct regulator_dev *dev)
+{
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int dcdc = rdev_get_id(dev);
+	int ret;
+	u8 shift;
+	unsigned char reg_ctrl;
+
+	if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3)
+		return -EINVAL;
+
+	shift = TPS6507X_MAX_REG_ID - dcdc;
+	ret = tps_6507x_read_reg(tps, TPS6507X_REG_CON_CTRL1, &reg_ctrl);
+
+	if (ret == 0) {
+		reg_ctrl &= (1 << shift);
+		return reg_ctrl ? 1 : 0;
+	} else
+		return ret;
+}
+
+static int tps6507x_ldo_is_enabled(struct regulator_dev *dev)
+{
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int ldo = rdev_get_id(dev);
+	int ret;
+	u8 shift;
+	unsigned char reg_ctrl;
+
+	if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
+		return -EINVAL;
+
+	shift = TPS6507X_MAX_REG_ID - ldo;
+	ret = tps_6507x_read_reg(tps, TPS6507X_REG_CON_CTRL1, &reg_ctrl);
+
+	if (ret == 0) {
+		reg_ctrl &= (1 << shift);
+		return reg_ctrl ? 1 : 0;
+	} else
+		return ret;
+}
+
+static int tps6507x_dcdc_enable(struct regulator_dev *dev)
+{
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int dcdc = rdev_get_id(dev);
+	u8 shift;
+
+	if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3)
+		return -EINVAL;
+
+	shift = TPS6507X_MAX_REG_ID - dcdc;
+	return tps_6507x_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
+}
+
+static int tps6507x_dcdc_disable(struct regulator_dev *dev)
+{
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int dcdc = rdev_get_id(dev);
+	u8 shift;
+
+	if (dcdc < TPS6507X_DCDC_1 || dcdc > TPS6507X_DCDC_3)
+		return -EINVAL;
+
+	shift = TPS6507X_MAX_REG_ID - dcdc;
+	return tps_6507x_clear_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
+}
+
+static int tps6507x_ldo_enable(struct regulator_dev *dev)
+{
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int ldo = rdev_get_id(dev);
+	u8 shift;
+
+	if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
+		return -EINVAL;
+
+	shift = TPS6507X_MAX_REG_ID - ldo;
+	return tps_6507x_set_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
+}
+
+static int tps6507x_ldo_disable(struct regulator_dev *dev)
+{
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int ldo = rdev_get_id(dev);
+	u8 shift;
+
+	if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
+		return -EINVAL;
+
+	shift = TPS6507X_MAX_REG_ID - ldo;
+	return tps_6507x_clear_bits(tps, TPS6507X_REG_CON_CTRL1, 1 << shift);
+}
+
+static int tps6507x_dcdc_get_voltage(struct regulator_dev *dev)
+{
+	unsigned char reg_ctrl;
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int dcdc = rdev_get_id(dev);
+	u8 reg;
+	int ret;
+
+	switch (dcdc) {
+	case TPS6507X_DCDC_1:
+		reg = TPS6507X_REG_DEFDCDC1;
+		break;
+	case TPS6507X_DCDC_2:
+		reg = TPS6507X_REG_DEFDCDC2_LOW;
+		break;
+	case TPS6507X_DCDC_3:
+		reg = TPS6507X_REG_DEFDCDC3_LOW;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
+	if (ret < 0)
+		return ret;
+
+	reg_ctrl &= TPS6507X_DEFDCDCX_DCDC_MASK;
+	return tps->info[dcdc]->table[reg_ctrl] * 1000;
+}
+
+static int tps6507x_dcdc_set_voltage(struct regulator_dev *dev,
+				int min_uV, int max_uV)
+{
+	unsigned char reg_ctrl;
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int dcdc = rdev_get_id(dev);
+	u8 reg;
+	int vsel, ret;
+
+	switch (dcdc) {
+	case TPS6507X_DCDC_1:
+		reg = TPS6507X_REG_DEFDCDC1;
+		break;
+	case TPS6507X_DCDC_2:
+		reg = TPS6507X_REG_DEFDCDC2_LOW;
+		break;
+	case TPS6507X_DCDC_3:
+		reg = TPS6507X_REG_DEFDCDC3_LOW;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (min_uV < tps->info[dcdc]->min_uV
+			|| min_uV > tps->info[dcdc]->max_uV)
+		return -EINVAL;
+	if (max_uV < tps->info[dcdc]->min_uV
+			|| max_uV > tps->info[dcdc]->max_uV)
+		return -EINVAL;
+
+	for (vsel = 0; vsel < tps->info[dcdc]->table_len; vsel++) {
+		int mV = tps->info[dcdc]->table[vsel];
+		int uV = mV * 1000;
+
+		/* Break at the first in-range value */
+		if (min_uV <= uV && uV <= max_uV)
+			break;
+	}
+
+	ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
+	if (ret < 0)
+		return ret;
+
+	reg_ctrl &= ~TPS6507X_DEFDCDCX_DCDC_MASK;
+	reg_ctrl |= vsel;
+
+	return tps_6507x_write_reg(tps, reg, reg_ctrl);
+}
+
+static int tps6507x_ldo_get_voltage(struct regulator_dev *dev)
+{
+	unsigned char reg_ctrl;
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int ldo = rdev_get_id(dev);
+	u8 reg, mask;
+	int ret;
+
+	if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
+		return -EINVAL;
+	else {
+		reg = (ldo == TPS6507X_LDO_1 ?
+			TPS6507X_REG_LDO_CTRL1 : TPS6507X_REG_DEFLDO2);
+		mask = (ldo == TPS6507X_LDO_1 ?
+			TPS6507X_REG_LDO_CTRL1_LDO1_MASK :
+				TPS6507X_REG_DEFLDO2_LDO2_MASK);
+	}
+
+	ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
+	if (ret < 0)
+		return ret;
+
+	reg_ctrl &= mask;
+	return tps->info[ldo]->table[reg_ctrl] * 1000;
+}
+
+static int tps6507x_ldo_set_voltage(struct regulator_dev *dev,
+				int min_uV, int max_uV)
+{
+	unsigned char reg_ctrl;
+	struct tps_pmic *tps = rdev_get_drvdata(dev);
+	int ldo = rdev_get_id(dev);
+	u8 reg, mask;
+	int vsel, ret;
+
+	if (ldo < TPS6507X_LDO_1 || ldo > TPS6507X_LDO_2)
+		return -EINVAL;
+	else {
+		reg = (ldo == TPS6507X_LDO_1 ?
+			TPS6507X_REG_LDO_CTRL1 : TPS6507X_REG_DEFLDO2);
+		mask = (ldo == TPS6507X_LDO_1 ?
+			TPS6507X_REG_LDO_CTRL1_LDO1_MASK :
+				TPS6507X_REG_DEFLDO2_LDO2_MASK);
+	}
+
+	if (min_uV < tps->info[ldo]->min_uV || min_uV > tps->info[ldo]->max_uV)
+		return -EINVAL;
+	if (max_uV < tps->info[ldo]->min_uV || max_uV > tps->info[ldo]->max_uV)
+		return -EINVAL;
+
+	for (vsel = 0; vsel < tps->info[ldo]->table_len; vsel++) {
+		int mV = tps->info[ldo]->table[vsel];
+		int uV = mV * 1000;
+
+		/* Break at the first in-range value */
+		if (min_uV <= uV && uV <= max_uV)
+			break;
+	}
+
+	ret = tps_6507x_read_reg(tps, reg, &reg_ctrl);
+	if (ret < 0)
+		return ret;
+
+	reg_ctrl &= ~mask;
+	reg_ctrl |= vsel;
+
+	return tps_6507x_write_reg(tps, reg, reg_ctrl);
+}
+
+/* Operations permitted on VDCDCx */
+static struct regulator_ops tps6507x_dcdc_ops = {
+	.is_enabled = tps6507x_dcdc_is_enabled,
+	.enable	= tps6507x_dcdc_enable,
+	.disable = tps6507x_dcdc_disable,
+	.get_voltage = tps6507x_dcdc_get_voltage,
+	.set_voltage = tps6507x_dcdc_set_voltage,
+};
+
+/* Operations permitted on LDOx */
+static struct regulator_ops tps6507x_ldo_ops = {
+	.is_enabled = tps6507x_ldo_is_enabled,
+	.enable	= tps6507x_ldo_enable,
+	.disable = tps6507x_ldo_disable,
+	.get_voltage = tps6507x_ldo_get_voltage,
+	.set_voltage = tps6507x_ldo_set_voltage,
+};
+
+static
+int tps_6507x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	static int desc_id;
+	const struct tps_info *info = (void *)id->driver_data;
+	struct regulator_init_data *init_data;
+	struct regulator_dev *rdev;
+	struct tps_pmic *tps;
+	int i;
+
+	if (!i2c_check_functionality(client->adapter,
+				I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	/**
+	 * init_data points to array of regulator_init structures
+	 * coming from the board-evm file.
+	 */
+	init_data = client->dev.platform_data;
+
+	if (!init_data)
+		return -EIO;
+
+	tps = kzalloc(sizeof(*tps), GFP_KERNEL);
+	if (!tps)
+		return -ENOMEM;
+
+	/* common for all regulators */
+	tps->client = client;
+
+	for (i = 0; i < TPS6507X_NUM_REGULATOR; i++, info++, init_data++) {
+		/* Register the regulators */
+		tps->info[i] = info;
+		tps->desc[i].name = info->name;
+		tps->desc[i].id = desc_id++;
+		tps->desc[i].ops = (i > TPS6507X_DCDC_3 ?
+				&tps6507x_ldo_ops : &tps6507x_dcdc_ops);
+		tps->desc[i].type = REGULATOR_VOLTAGE;
+		tps->desc[i].owner = THIS_MODULE;
+
+		rdev = regulator_register(&tps->desc[i],
+					&client->dev, init_data, tps);
+		if (IS_ERR(rdev)) {
+			dev_err(&client->dev, "failed to register %s\n",
+					id->name);
+			kfree(tps);
+			return PTR_ERR(rdev);
+		}
+
+		/* Save regulator for cleanup */
+		tps->rdev[i] = rdev;
+	}
+
+	i2c_set_clientdata(client, tps);
+
+	return 0;
+}
+
+/**
+ * tps_6507x_remove - TPS6507x driver i2c remove handler
+ * @client: i2c driver client device structure
+ *
+ * Unregister TPS driver as an i2c client device driver
+ */
+static int __devexit tps_6507x_remove(struct i2c_client *client)
+{
+	struct tps_pmic *tps = i2c_get_clientdata(client);
+	int i;
+
+	for (i = 0; i < TPS6507X_NUM_REGULATOR; i++)
+		regulator_unregister(tps->rdev[i]);
+
+	tps->client = NULL;
+
+	/* clear the client data in i2c */
+	i2c_set_clientdata(client, NULL);
+	kfree(tps);
+
+	return 0;
+}
+
+/* Supported voltage values for regulators (in milliVolts) */
+static const u16 VDCDCx_VSEL_table[] = {
+	725, 750, 775, 800,
+	825, 850, 875, 900,
+	925, 950, 975, 1000,
+	1025, 1050, 1075, 1100,
+	1125, 1150, 1175, 1200,
+	1225, 1250, 1275, 1300,
+	1325, 1350, 1375, 1400,
+	1425, 1450, 1475, 1500,
+	1550, 1600, 1650, 1700,
+	1750, 1800, 1850, 1900,
+	1950, 2000, 2050, 2100,
+	2150, 2200, 2250, 2300,
+	2350, 2400, 2450, 2500,
+	2550, 2600, 2650, 2700,
+	2750, 2800, 2850, 2900,
+	3000, 3100, 3200, 3300,
+};
+
+static const u16 LDO1_VSEL_table[] = {
+	1000, 1100, 1200, 1250,
+	1300, 1350, 1400, 1500,
+	1600, 1800, 2500, 2750,
+	2800, 3000, 3100, 3300,
+};
+
+static const u16 LDO2_VSEL_table[] = {
+	725, 750, 775, 800,
+	825, 850, 875, 900,
+	925, 950, 975, 1000,
+	1025, 1050, 1075, 1100,
+	1125, 1150, 1175, 1200,
+	1225, 1250, 1275, 1300,
+	1325, 1350, 1375, 1400,
+	1425, 1450, 1475, 1500,
+	1550, 1600, 1650, 1700,
+	1750, 1800, 1850, 1900,
+	1950, 2000, 2050, 2100,
+	2150, 2200, 2250, 2300,
+	2350, 2400, 2450, 2500,
+	2550, 2600, 2650, 2700,
+	2750, 2800, 2850, 2900,
+	3000, 3100, 3200, 3300,
+};
+
+static const struct tps_info tps6507x_regs[] = {
+	{
+	.name = "VDCDC1",
+	.min_uV = 725000,
+	.max_uV = 3300000,
+	.fixed = 0,
+	.table_len = ARRAY_SIZE(VDCDCx_VSEL_table),
+	.table = VDCDCx_VSEL_table,
+	},
+	{
+	.name = "VDCDC2",
+	.min_uV = 725000,
+	.max_uV = 3300000,
+	.fixed = 0,
+	.table_len = ARRAY_SIZE(VDCDCx_VSEL_table),
+	.table = VDCDCx_VSEL_table,
+	},
+	{
+	.name = "VDCDC3",
+	.min_uV = 725000,
+	.max_uV = 3300000,
+	.fixed = 0,
+	.table_len = ARRAY_SIZE(VDCDCx_VSEL_table),
+	.table = VDCDCx_VSEL_table,
+	},
+	{
+	.name = "LDO1",
+	.min_uV = 1000000,
+	.max_uV = 3300000,
+	.fixed = 0,
+	.table_len = ARRAY_SIZE(LDO1_VSEL_table),
+	.table = LDO1_VSEL_table,
+	},
+	{
+	.name = "LDO2",
+	.min_uV = 725000,
+	.max_uV = 3300000,
+	.fixed = 0,
+	.table_len = ARRAY_SIZE(LDO2_VSEL_table),
+	.table = LDO2_VSEL_table,
+	},
+};
+
+static const struct i2c_device_id tps_6507x_id = {
+	.name = "tps6507x",
+	.driver_data = (unsigned long) &tps6507x_regs[0],
+};
+MODULE_DEVICE_TABLE(i2c, tps_6507x_id);
+
+static struct i2c_driver tps_6507x_i2c_driver = {
+	.driver = {
+		.name = "tps_6507x_pwr",
+		.owner = THIS_MODULE,
+	},
+	.probe = tps_6507x_probe,
+	.remove = __devexit_p(tps_6507x_remove),
+	.id_table = &tps_6507x_id,
+};
+
+/**
+ * tps_6507x_init
+ *
+ * Module init function
+ */
+static int __init tps_6507x_init(void)
+{
+	return i2c_add_driver(&tps_6507x_i2c_driver);
+}
+subsys_initcall(tps_6507x_init);
+
+/**
+ * tps_6507x_cleanup
+ *
+ * Module exit function
+ */
+static void __exit tps_6507x_cleanup(void)
+{
+	i2c_del_driver(&tps_6507x_i2c_driver);
+}
+module_exit(tps_6507x_cleanup);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("TPS6507x voltage regulator driver");
+MODULE_LICENSE("GPLv2");