aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/regulator/lp8720.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/regulator/lp8720.c')
-rw-r--r--drivers/regulator/lp8720.c471
1 files changed, 471 insertions, 0 deletions
diff --git a/drivers/regulator/lp8720.c b/drivers/regulator/lp8720.c
new file mode 100644
index 0000000..cb66718
--- /dev/null
+++ b/drivers/regulator/lp8720.c
@@ -0,0 +1,471 @@
+/*
+ * Regulator driver for National Semiconductors LP8720 PMIC chip
+ *
+ * Based on lp3972.c
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/bug.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/lp8720.h>
+#include <mach/gpio.h>
+#include <linux/slab.h>
+#include <plat/gpio-cfg.h>
+
+static const int ldo_output_enable_mask[] = {
+ LP8720_LDO1_EN,
+ LP8720_LDO2_EN,
+ LP8720_LDO3_EN,
+ LP8720_LDO4_EN,
+ LP8720_LDO5_EN,
+ LP8720_BUCK_V1_EN,
+ LP8720_BUCK_V2_EN,
+};
+
+static const int ldo_output_enable_addr[] = {
+ LP8720_LDO1_REG,
+ LP8720_LDO2_REG,
+ LP8720_LDO3_REG,
+ LP8720_LDO4_REG,
+ LP8720_LDO5_REG,
+ LP8720_BUCK_V1_REG,
+ LP8720_BUCK_V2_REG,
+};
+
+#define LP8720_LDO_OUTPUT_ENABLE_MASK(x) (ldo_output_enable_mask[x])
+#define LP8720_LDO_OUTPUT_ENABLE_REG(x) (ldo_output_enable_addr[x])
+
+
+static const int ldo1235_voltage_map[] = {
+ 1200, 1250, 1300, 1350, 1400, 1450, 1500, 1550,
+ 1600, 1650, 1700, 1750, 1800, 1850, 1900, 2000,
+ 2100, 2200, 2300, 2400, 2500, 2600, 2650, 2700,
+ 2750, 2800, 2850, 2900, 2950, 3000, 3100, 3300,
+};
+
+static const int ldo4_voltage_map[] = {
+ 800, 850, 900, 1000, 1100, 1200, 1250, 1300,
+ 1350, 1400, 1450, 1500, 1550, 1600, 1650, 1700,
+ 1750, 1800, 1850, 1900, 2000, 2100, 2200, 2300,
+ 2400, 2500, 2600, 2650, 2700, 2750, 2800, 2850,
+};
+
+static const int buck12_voltage_map[] = {
+ 0, 800, 850, 900, 950, 1000, 1050, 1100,
+ 1150, 1200, 1250, 1300, 1350, 1400, 1450, 1500,
+ 1550, 1600, 1650, 1700, 1750, 1800, 1850, 1900,
+ 1950, 2000, 2050, 2100, 2150, 2200, 2250, 2300,
+};
+
+static const int *ldo_voltage_map[] = {
+ ldo1235_voltage_map, /* LDO1 */
+ ldo1235_voltage_map, /* LDO2 */
+ ldo1235_voltage_map, /* LDO3 */
+ ldo4_voltage_map, /* LDO4 */
+ ldo1235_voltage_map, /* LDO5 */
+ buck12_voltage_map, /* BUCK_V1 */
+ buck12_voltage_map, /* BUCK_V2 */
+};
+
+#define LDO_VOL_VALUE_MAP(x) (ldo_voltage_map[(x - LP8720_LDO1)])
+#define LDO_VOL_MIN_IDX (0)
+#define LDO_VOL_MAX_IDX (31)
+
+static int lp8720_i2c_read(struct i2c_client *i2c, char reg, int count,
+ u8 *dest)
+{
+ u8 ret;
+
+ if (count != 1)
+ return -EIO;
+
+ ret = i2c_smbus_read_byte_data(i2c, reg);
+
+ if (ret < 0)
+ return ret;
+
+ *dest = ret;
+ return 0;
+}
+
+static int lp8720_i2c_write(struct i2c_client *i2c, u8 reg, int count,
+ const u8 value)
+{
+ u8 ret;
+
+ if (count != 1)
+ return -EIO;
+
+ ret = i2c_smbus_write_byte_data(i2c, reg, value);
+
+ return ret;
+}
+
+static u8 lp8720_reg_read(struct lp8720 *lp8720, u8 reg)
+{
+ u8 val = 0;
+
+ mutex_lock(&lp8720->io_lock);
+ lp8720_i2c_read(lp8720->i2c, reg, 1, &val);
+ dev_dbg(lp8720->dev, "reg read 0x%02x -> 0x%02x\n", (int)reg,
+ (unsigned)val & 0xff);
+ mutex_unlock(&lp8720->io_lock);
+
+ return val & 0xff;
+}
+
+
+static int lp8720_set_bits(struct lp8720 *lp8720, u8 reg, u16 mask, u16 val)
+{
+ u8 tmp;
+ int ret;
+
+ mutex_lock(&lp8720->io_lock);
+ ret = lp8720_i2c_read(lp8720->i2c, reg, 1, &tmp);
+ tmp = (tmp & ~mask) | val;
+ if (ret == 0) {
+ ret = lp8720_i2c_write(lp8720->i2c, reg, 1, tmp);
+ dev_dbg(lp8720->dev, "reg write 0x%02x -> 0x%02x\n", (int)reg,
+ (unsigned)val & 0xff);
+ }
+ mutex_unlock(&lp8720->io_lock);
+
+ return ret;
+}
+
+static int lp8720_ldo_is_enabled(struct regulator_dev *dev)
+{
+ struct lp8720 *lp8720 = rdev_get_drvdata(dev);
+ int ldo = rdev_get_id(dev) - LP8720_LDO1;
+ u16 mask = LP8720_LDO_OUTPUT_ENABLE_MASK(ldo);
+ u16 val;
+
+ val = lp8720_reg_read(lp8720, LP8720_ENABLE_REG);
+ dev_dbg(lp8720->dev, "%s, is_enabled ldo:%d, mask:0x%4x - %s\n",
+ __func__, ldo, mask,
+ !!(val & mask) ? "enabled" : "disabled");
+
+ return !!(val & mask);
+}
+
+static int lp8720_ldo_enable(struct regulator_dev *dev)
+{
+ struct lp8720 *lp8720 = rdev_get_drvdata(dev);
+ int ldo = rdev_get_id(dev) - LP8720_LDO1;
+ u16 mask = LP8720_LDO_OUTPUT_ENABLE_MASK(ldo);
+
+ dev_dbg(lp8720->dev, "%s, enable ldo:%d, mask:0x%4x\n",
+ __func__, ldo, mask);
+ return lp8720_set_bits(lp8720, LP8720_ENABLE_REG,
+ mask, mask);
+}
+
+static int lp8720_ldo_disable(struct regulator_dev *dev)
+{
+ struct lp8720 *lp8720 = rdev_get_drvdata(dev);
+ int ldo = rdev_get_id(dev) - LP8720_LDO1;
+ u16 mask = LP8720_LDO_OUTPUT_ENABLE_MASK(ldo);
+
+ dev_dbg(lp8720->dev, "%s, disable ldo:%d, mask:0x%4x\n",
+ __func__, ldo, mask);
+ return lp8720_set_bits(lp8720, LP8720_ENABLE_REG,
+ mask, 0);
+}
+
+static int lp8720_ldo_list_voltage(struct regulator_dev *dev, unsigned index)
+{
+ struct lp8720 *lp8720 = rdev_get_drvdata(dev);
+ int ldo = rdev_get_id(dev) - LP8720_LDO1;
+
+ if (ldo < 0 || ldo > ARRAY_SIZE(ldo_voltage_map)) {
+ dev_err(lp8720->dev, "[ldo = %d] is out of range\n", ldo);
+ return -EINVAL;
+ }
+ if (index < 0 || index > LDO_VOL_MAX_IDX) {
+ dev_err(lp8720->dev, "[index = %d] is out of range\n", index);
+ return -EINVAL;
+ }
+ return 1000 * LDO_VOL_VALUE_MAP(ldo)[index];
+}
+
+static int lp8720_ldo_get_voltage(struct regulator_dev *dev)
+{
+ struct lp8720 *lp8720 = rdev_get_drvdata(dev);
+ int ldo = rdev_get_id(dev) - LP8720_LDO1;
+ u16 val, reg;
+
+ reg = lp8720_reg_read(lp8720, LP8720_LDO_VOL_CONTR_REG(ldo));
+ val = reg & LP8720_LDOV_MASK;
+
+ return 1000 * LDO_VOL_VALUE_MAP(ldo)[val];
+}
+
+static int lp8720_ldo_set_voltage(struct regulator_dev *dev,
+ int min_uV, int max_uV,
+ unsigned int *selector)
+{
+ struct lp8720 *lp8720 = rdev_get_drvdata(dev);
+ int ldo = rdev_get_id(dev) - LP8720_LDO1;
+ int min_vol = min_uV / 1000, max_vol = max_uV / 1000;
+ const int *vol_map = LDO_VOL_VALUE_MAP(ldo);
+ u16 val;
+
+ if (min_vol < vol_map[LDO_VOL_MIN_IDX] ||
+ min_vol > vol_map[LDO_VOL_MAX_IDX]) {
+ dev_err(lp8720->dev, "[min_vol = %d] is out of range\n",
+ min_vol);
+ dev_err(lp8720->dev, "vol_map[%d] : %d, vol_map[%d] : %d\n",
+ LDO_VOL_MIN_IDX, vol_map[LDO_VOL_MIN_IDX],
+ LDO_VOL_MAX_IDX, vol_map[LDO_VOL_MAX_IDX]);
+ return -EINVAL;
+ }
+
+ for (val = LDO_VOL_MIN_IDX; val <= LDO_VOL_MAX_IDX; val++)
+ if (vol_map[val] >= min_vol)
+ break;
+
+ if (val > LDO_VOL_MAX_IDX || vol_map[val] > max_vol) {
+ dev_err(lp8720->dev, "[val = %d] is out of range\n", val);
+ return -EINVAL;
+ }
+
+ *selector = val;
+ dev_dbg(lp8720->dev, "%s, disable ldo:%d, val:0x%4x\n",
+ __func__, ldo, val);
+
+ return lp8720_set_bits(lp8720, LP8720_LDO_VOL_CONTR_REG(ldo),
+ LP8720_LDOV_MASK, val);
+}
+
+static struct regulator_ops lp8720_ldo_ops = {
+ .list_voltage = lp8720_ldo_list_voltage,
+ .is_enabled = lp8720_ldo_is_enabled,
+ .enable = lp8720_ldo_enable,
+ .disable = lp8720_ldo_disable,
+ .get_voltage = lp8720_ldo_get_voltage,
+ .set_voltage = lp8720_ldo_set_voltage,
+};
+
+static struct regulator_desc regulators[] = {
+ {
+ .name = "LDO1",
+ .id = LP8720_LDO1,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(ldo1235_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ {
+ .name = "LDO2",
+ .id = LP8720_LDO2,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(ldo1235_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ {
+ .name = "LDO3",
+ .id = LP8720_LDO3,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(ldo1235_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ {
+ .name = "LDO4",
+ .id = LP8720_LDO4,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(ldo4_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ {
+ .name = "LDO5",
+ .id = LP8720_LDO5,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(ldo1235_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ {
+ .name = "BUCK_V1",
+ .id = LP8720_BUCK_V1,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(buck12_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+ {
+ .name = "BUCK_V2",
+ .id = LP8720_BUCK_V2,
+ .ops = &lp8720_ldo_ops,
+ .n_voltages = ARRAY_SIZE(buck12_voltage_map),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __devinit setup_regulators(struct lp8720 *lp8720,
+ struct lp8720_platform_data *pdata)
+{
+ int i, err;
+
+ lp8720->num_regulators = pdata->num_regulators;
+ lp8720->rdev = kcalloc(pdata->num_regulators,
+ sizeof(struct regulator_dev *), GFP_KERNEL);
+ if (!lp8720->rdev) {
+ err = -ENOMEM;
+ goto err_nomem;
+ }
+
+ /* Instantiate the regulators */
+ for (i = 0; i < pdata->num_regulators; i++) {
+ struct lp8720_regulator_subdev *reg = &pdata->regulators[i];
+ lp8720->rdev[i] = regulator_register(&regulators[reg->id],
+ lp8720->dev, reg->initdata, lp8720);
+ if (IS_ERR(lp8720->rdev[i])) {
+ err = PTR_ERR(lp8720->rdev[i]);
+ dev_err(lp8720->dev, "regulator init failed: %d\n",
+ err);
+ goto error;
+ }
+ }
+ return 0;
+error:
+ while (--i >= 0)
+ regulator_unregister(lp8720->rdev[i]);
+ kfree(lp8720->rdev);
+ lp8720->rdev = NULL;
+err_nomem:
+ return err;
+}
+
+static int __devinit lp8720_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct lp8720 *lp8720;
+ struct lp8720_platform_data *pdata = i2c->dev.platform_data;
+ int ret;
+ u8 readbyte = 0;
+ u8 enable = 0;
+
+ if (!pdata) {
+ dev_dbg(&i2c->dev, "No platform init data supplied\n");
+ return -ENODEV;
+ }
+
+ lp8720 = kzalloc(sizeof(struct lp8720), GFP_KERNEL);
+ if (!lp8720)
+ return -ENOMEM;
+
+ lp8720->i2c = i2c;
+ lp8720->dev = &i2c->dev;
+
+ mutex_init(&lp8720->io_lock);
+ ret = lp8720_i2c_read(i2c, 0x00, 1, &readbyte);
+ if (ret == 0 &&
+ readbyte != 0x05) {
+ ret = -ENODEV;
+ dev_err(&i2c->dev, "chip reported: [00h]= 0x%x\n", readbyte);
+ }
+ if (ret < 0) {
+ dev_err(&i2c->dev, "failed to detect device. ret = %d\n", ret);
+ goto err_detect;
+ }
+
+ ret = setup_regulators(lp8720, pdata);
+ if (ret < 0)
+ goto err_detect;
+
+ i2c_set_clientdata(i2c, lp8720);
+ ret = gpio_request(pdata->en_pin, pdata->name);
+ if (ret) {
+ printk(KERN_ERR "%s, ERROR [%s] - gpio_request(%d) failed\n",
+ __func__,
+ pdata->name,
+ pdata->en_pin);
+ }
+ s3c_gpio_cfgpin(pdata->en_pin, S3C_GPIO_OUTPUT);
+ s3c_gpio_setpull(pdata->en_pin, S3C_GPIO_PULL_NONE);
+ gpio_set_value(pdata->en_pin, 1);
+ if (!gpio_get_value(pdata->en_pin)) {
+ printk(KERN_ERR "%s, ERROR [%s] - gpio_get_value(%d) is %s\n",
+ __func__,
+ pdata->name,
+ pdata->en_pin,
+ gpio_get_value(pdata->en_pin) ? "HIGH" : "LOW");
+ }
+ if (!strncmp(pdata->name,
+ "lp8720_folder_pmic",
+ strlen("lp8720_folder_pmic"))) {
+ printk(KERN_DEBUG "%s, folder_pmic\n", __func__);
+ } else if (!strncmp(pdata->name,
+ "lp8720_sub_pmic",
+ strlen("lp8720_sub_pmic"))) {
+ lp8720_i2c_read(i2c, LP8720_ENABLE_REG, 1, &readbyte);
+ enable = readbyte & 0xC0;
+ lp8720_i2c_write(i2c, LP8720_ENABLE_REG, 1, enable);
+ lp8720_i2c_read(i2c, LP8720_ENABLE_REG, 1, &readbyte);
+ printk(KERN_DEBUG "%s, [%s] - ENABLE_REG : 0x%2x\n",
+ __func__, pdata->name, readbyte);
+ }
+
+ return 0;
+
+err_detect:
+ kfree(lp8720);
+ return ret;
+}
+
+static int __devexit lp8720_i2c_remove(struct i2c_client *i2c)
+{
+ struct lp8720 *lp8720 = i2c_get_clientdata(i2c);
+ struct lp8720_platform_data *pdata = i2c->dev.platform_data;
+ int i;
+
+ for (i = 0; i < lp8720->num_regulators; i++)
+ regulator_unregister(lp8720->rdev[i]);
+ kfree(lp8720->rdev);
+ kfree(lp8720);
+ gpio_free(pdata->en_pin);
+ return 0;
+}
+
+static const struct i2c_device_id lp8720_i2c_id[] = {
+ { "lp8720", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp8720_i2c_id);
+
+static struct i2c_driver lp8720_i2c_driver = {
+ .driver = {
+ .name = "lp8720",
+ .owner = THIS_MODULE,
+ },
+ .probe = lp8720_i2c_probe,
+ .remove = __devexit_p(lp8720_i2c_remove),
+ .id_table = lp8720_i2c_id,
+};
+
+static int __init lp8720_module_init(void)
+{
+ return i2c_add_driver(&lp8720_i2c_driver);
+}
+subsys_initcall(lp8720_module_init);
+
+static void __exit lp8720_module_exit(void)
+{
+ i2c_del_driver(&lp8720_i2c_driver);
+}
+module_exit(lp8720_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Axel Lin <axel.lin@gmail.com>");
+MODULE_DESCRIPTION("LP8720 PMIC driver");